@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.
- package/Dockerfile.package +4 -5
- package/README.md +113 -8
- package/bun.lock +2 -2
- package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
- package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
- package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
- package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
- package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
- package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
- package/dist/__tests__/preload.d.ts +1 -4
- package/dist/__tests__/preload.d.ts.map +1 -1
- package/dist/cli/desktop-snapshot.d.ts +20 -0
- package/dist/cli/desktop-snapshot.d.ts.map +1 -0
- package/dist/cli/index.js +2094 -280
- package/dist/cli/macos-permissions.d.ts +72 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -1
- package/dist/cli/macos-shortcut.d.ts +139 -0
- package/dist/cli/macos-shortcut.d.ts.map +1 -0
- package/dist/cli/trigger-probe.d.ts +172 -0
- package/dist/cli/trigger-probe.d.ts.map +1 -0
- package/dist/db/database.d.ts +9 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/http/client.d.ts +19 -7
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -50
- package/dist/lib/capture-probe.d.ts +216 -0
- package/dist/lib/capture-probe.d.ts.map +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/macos-bundle.d.ts +50 -0
- package/dist/lib/macos-bundle.d.ts.map +1 -0
- package/dist/lib/persistence-probe.d.ts +208 -0
- package/dist/lib/persistence-probe.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/retired-deployment-modes.d.ts +24 -0
- package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts +60 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.js +141 -57
- package/dist/server/cloud-config.d.ts +25 -3
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud-readiness.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +8 -7
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +188 -80
- package/dist/server/migrate-command.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +109 -50
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +25 -6
- package/scripts/ci-linux-suite.ts +403 -0
- package/scripts/ci-native-build.ts +402 -0
- package/scripts/enforce_identity_migration.sh +110 -0
- package/scripts/generate-sdk.ts +1 -1
- package/scripts/install_macos_app.sh +192 -31
- package/scripts/macos_artifact.ts +323 -43
- package/scripts/native/realtime-settle-probe/README.md +39 -0
- package/scripts/native/realtime-settle-probe/main.swift +135 -0
- package/scripts/policy/local-only-approved-targets.txt +21 -0
- package/scripts/read_local_only_targets.sh +203 -0
- package/scripts/scan-artifact.ts +40 -0
- package/scripts/set-version.ts +145 -0
- package/scripts/smoke_macos_app.sh +62 -16
- package/scripts/vacuity-manifest-gen.ts +96 -0
- package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
- package/scripts/vacuity-manifests/install-chain.tsv +13 -0
- package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
- package/scripts/vacuity-manifests/reorder.tsv +4 -0
- package/scripts/vacuity-manifests/source-side.tsv +15 -0
- package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
- package/scripts/vacuity-manifests/version-sites.tsv +61 -0
- package/scripts/vacuity-mutation-battery.ts +266 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +23 -11
- package/src/native/Recordings/App/RecordingsApp.swift +37 -1
- package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
- package/src/native/Recordings/App/SidebarView.swift +1 -1
- package/src/native/Recordings/Package.swift +24 -5
- package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
- package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
- package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
- package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +21 -1
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
- package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
- package/src/native/Recordings/RecordingsTests/BarOnlyLaunchPlanTests.swift +99 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
- package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
- package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
- package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
- package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
- package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
- package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
- package/src/native/Recordings/build.sh +226 -20
- 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
|
-
|
|
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
|
-
|
|
4504
|
-
|
|
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
|
-
|
|
4507
|
-
|
|
4517
|
+
const singleQuotedValue = match2?.[1];
|
|
4518
|
+
if (singleQuotedValue !== undefined)
|
|
4519
|
+
return singleQuotedValue;
|
|
4508
4520
|
const match3 = content.match(new RegExp(`${keyName}\\s*=\\s*(.+)`));
|
|
4509
|
-
|
|
4510
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
5089
|
+
var SERVER_BACKEND_VALUES = ["sqlite", "postgresql", "postgres"];
|
|
5051
5090
|
function envToken(name) {
|
|
5052
5091
|
return name.toUpperCase().replace(/-/g, "_");
|
|
5053
5092
|
}
|
|
5054
|
-
function
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
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
|
|
5105
|
+
throw new Error(`Unknown client store: ${value}. Use sqlite or http.`);
|
|
5064
5106
|
}
|
|
5065
|
-
function
|
|
5066
|
-
return `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5103
|
-
let deprecatedAlias = null;
|
|
5160
|
+
let requested = "sqlite";
|
|
5104
5161
|
let modeSource = "default";
|
|
5105
|
-
if (
|
|
5106
|
-
|
|
5107
|
-
|
|
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
|
-
|
|
5166
|
+
requested = "http";
|
|
5112
5167
|
modeSource = "auto:api-url+api-key";
|
|
5113
5168
|
}
|
|
5114
|
-
if (
|
|
5115
|
-
return { transport: "
|
|
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: "
|
|
5120
|
-
|
|
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}=
|
|
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 ??
|
|
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: "
|
|
5189
|
+
return { transport: "sqlite", requested, modeSource, baseUrl: null, apiKeyPresent: true, misconfigured: true, warning: `Invalid API URL: ${message}.` };
|
|
5136
5190
|
}
|
|
5137
|
-
return { transport: "
|
|
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
|
|
5376
|
+
throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for the /v1 API.`);
|
|
5321
5377
|
}
|
|
5322
|
-
if (resolution.transport === "
|
|
5323
|
-
return { transport: "
|
|
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
|
|
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: "
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
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 === "
|
|
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
|
-
/**
|
|
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":"
|
|
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,
|
|
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"}
|
package/dist/server/cloud.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* PostgreSQL-backed `/v1` service wiring for `recordings-serve`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* DIRECTLY through the repo-native async adapter (`PgAdapterAsync`) —
|
|
6
|
-
* NO
|
|
7
|
-
* Postgres or crypto until the first `/v1` (or `/ready`) request, so the
|
|
8
|
-
*
|
|
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 {
|
|
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;
|
|
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"}
|