@hasna/recordings 0.3.8 → 0.3.10
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/bun.lock +26 -26
- package/dist/cli/index.js +915 -164
- package/dist/http/client.d.ts +6 -4
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.js +1751 -44
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/mcp/index.js +872 -121
- package/dist/server/index.js +52 -192
- package/dist/storage.js +1744 -37
- package/dist/store.d.ts.map +1 -1
- package/package.json +2 -2
- package/scripts/release-suite-gate.ts +17 -24
- package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
package/dist/server/index.js
CHANGED
|
@@ -22,7 +22,7 @@ var package_default;
|
|
|
22
22
|
var init_package = __esm(() => {
|
|
23
23
|
package_default = {
|
|
24
24
|
name: "@hasna/recordings",
|
|
25
|
-
version: "0.3.
|
|
25
|
+
version: "0.3.10",
|
|
26
26
|
type: "module",
|
|
27
27
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
28
28
|
repository: {
|
|
@@ -106,7 +106,7 @@ var init_package = __esm(() => {
|
|
|
106
106
|
"LICENSE"
|
|
107
107
|
],
|
|
108
108
|
dependencies: {
|
|
109
|
-
"@hasna/contracts": "0.13.
|
|
109
|
+
"@hasna/contracts": "0.13.4",
|
|
110
110
|
"@hasna/events": "0.1.11",
|
|
111
111
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
112
112
|
chalk: "^5.4.1",
|
|
@@ -6791,6 +6791,11 @@ var init_feedback = __esm(() => {
|
|
|
6791
6791
|
});
|
|
6792
6792
|
|
|
6793
6793
|
// src/http/client.ts
|
|
6794
|
+
import { createClientTransport } from "@hasna/contracts/client";
|
|
6795
|
+
import {
|
|
6796
|
+
createHasnaStorageClient,
|
|
6797
|
+
resolveStorageClient
|
|
6798
|
+
} from "@hasna/contracts/client/storage";
|
|
6794
6799
|
function envToken(name) {
|
|
6795
6800
|
return name.toUpperCase().replace(/-/g, "_");
|
|
6796
6801
|
}
|
|
@@ -6892,199 +6897,38 @@ function resolveTransport(name, env = process.env) {
|
|
|
6892
6897
|
}
|
|
6893
6898
|
return { transport: "http", requested, modeSource, baseUrl, apiKeyPresent: true, misconfigured: false, warning: null };
|
|
6894
6899
|
}
|
|
6895
|
-
function
|
|
6896
|
-
if (!query)
|
|
6897
|
-
return path;
|
|
6898
|
-
const params = new URLSearchParams;
|
|
6899
|
-
for (const [key, value] of Object.entries(query)) {
|
|
6900
|
-
if (value === null || value === undefined)
|
|
6901
|
-
continue;
|
|
6902
|
-
if (Array.isArray(value))
|
|
6903
|
-
for (const v of value)
|
|
6904
|
-
params.append(key, String(v));
|
|
6905
|
-
else
|
|
6906
|
-
params.append(key, String(value));
|
|
6907
|
-
}
|
|
6908
|
-
const qs = params.toString();
|
|
6909
|
-
return qs ? `${path}${path.includes("?") ? "&" : "?"}${qs}` : path;
|
|
6910
|
-
}
|
|
6911
|
-
function createHttpTransport(options) {
|
|
6912
|
-
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
6913
|
-
const base = options.baseUrl.replace(/\/+$/, "");
|
|
6914
|
-
const timeoutMs = options.timeoutMs ?? 30000;
|
|
6915
|
-
const sleep = options.sleepImpl ?? defaultSleep;
|
|
6916
|
-
async function once(method, rel, url, body, opts) {
|
|
6917
|
-
const headers = {
|
|
6918
|
-
"x-api-key": options.apiKey,
|
|
6919
|
-
Authorization: `Bearer ${options.apiKey}`,
|
|
6920
|
-
Accept: "application/json",
|
|
6921
|
-
...opts.headers ?? {}
|
|
6922
|
-
};
|
|
6923
|
-
if (opts.idempotencyKey)
|
|
6924
|
-
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
6925
|
-
const init = { method, headers };
|
|
6926
|
-
if (body !== undefined) {
|
|
6927
|
-
headers["Content-Type"] = "application/json";
|
|
6928
|
-
init.body = JSON.stringify(body);
|
|
6929
|
-
}
|
|
6930
|
-
const controller = new AbortController;
|
|
6931
|
-
const onAbort = () => controller.abort();
|
|
6932
|
-
if (opts.signal) {
|
|
6933
|
-
if (opts.signal.aborted)
|
|
6934
|
-
controller.abort();
|
|
6935
|
-
else
|
|
6936
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
6937
|
-
}
|
|
6938
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
|
|
6939
|
-
init.signal = controller.signal;
|
|
6940
|
-
let response;
|
|
6941
|
-
try {
|
|
6942
|
-
response = await fetchImpl(url, init);
|
|
6943
|
-
} catch (error2) {
|
|
6944
|
-
const err = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
6945
|
-
if (opts.signal?.aborted)
|
|
6946
|
-
return { ok: false, retryable: false, error: err };
|
|
6947
|
-
return { ok: false, retryable: true, error: err };
|
|
6948
|
-
} finally {
|
|
6949
|
-
clearTimeout(timer);
|
|
6950
|
-
if (opts.signal)
|
|
6951
|
-
opts.signal.removeEventListener("abort", onAbort);
|
|
6952
|
-
}
|
|
6953
|
-
const text = await response.text();
|
|
6954
|
-
let parsed = undefined;
|
|
6955
|
-
if (text.length > 0) {
|
|
6956
|
-
try {
|
|
6957
|
-
parsed = JSON.parse(text);
|
|
6958
|
-
} catch {
|
|
6959
|
-
parsed = text;
|
|
6960
|
-
}
|
|
6961
|
-
}
|
|
6962
|
-
if (!response.ok) {
|
|
6963
|
-
return { ok: false, retryable: RETRY_STATUSES.has(response.status), error: new HasnaHttpError(method, rel, response.status, parsed) };
|
|
6964
|
-
}
|
|
6965
|
-
return { ok: true, value: parsed };
|
|
6966
|
-
}
|
|
6967
|
-
async function request(method, path, body, opts = {}) {
|
|
6968
|
-
const upper = method.toUpperCase();
|
|
6969
|
-
const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
|
|
6970
|
-
const url = `${base}${rel}`;
|
|
6971
|
-
const methodRetryable = IDEMPOTENT.has(upper) || Boolean(opts.idempotencyKey);
|
|
6972
|
-
const maxRetries = opts.retries ?? 2;
|
|
6973
|
-
const maxAttempts = methodRetryable ? maxRetries + 1 : 1;
|
|
6974
|
-
let last = null;
|
|
6975
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
6976
|
-
const result = await once(upper, rel, url, body, opts);
|
|
6977
|
-
if (result.ok)
|
|
6978
|
-
return result.value;
|
|
6979
|
-
last = result;
|
|
6980
|
-
const canRetry = methodRetryable && result.retryable && attempt < maxAttempts;
|
|
6981
|
-
if (!canRetry)
|
|
6982
|
-
break;
|
|
6983
|
-
const backoff = Math.min(2000, 200 * 2 ** (attempt - 1));
|
|
6984
|
-
const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
|
|
6985
|
-
await sleep(backoff + jitter);
|
|
6986
|
-
}
|
|
6987
|
-
if (last === null)
|
|
6988
|
-
throw new Error(`Request to ${rel} completed without a result`);
|
|
6989
|
-
throw last.error;
|
|
6990
|
-
}
|
|
6991
|
-
return {
|
|
6992
|
-
baseUrl: base,
|
|
6993
|
-
request,
|
|
6994
|
-
get: (path, opts) => request("GET", path, undefined, opts),
|
|
6995
|
-
post: (path, body, opts) => request("POST", path, body, opts),
|
|
6996
|
-
patch: (path, body, opts) => request("PATCH", path, body, opts),
|
|
6997
|
-
put: (path, body, opts) => request("PUT", path, body, opts),
|
|
6998
|
-
del: (path, body, opts) => request("DELETE", path, body, opts)
|
|
6999
|
-
};
|
|
7000
|
-
}
|
|
7001
|
-
function newIdempotencyKey() {
|
|
7002
|
-
const g = globalThis;
|
|
7003
|
-
if (g.crypto?.randomUUID)
|
|
7004
|
-
return g.crypto.randomUUID();
|
|
7005
|
-
return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
|
|
7006
|
-
}
|
|
7007
|
-
function extractItems(raw, extraKeys = []) {
|
|
7008
|
-
if (Array.isArray(raw))
|
|
7009
|
-
return raw;
|
|
7010
|
-
if (raw && typeof raw === "object") {
|
|
7011
|
-
const obj = raw;
|
|
7012
|
-
for (const key of [...extraKeys, "items", "data", "results", "rows", "records"]) {
|
|
7013
|
-
if (Array.isArray(obj[key]))
|
|
7014
|
-
return obj[key];
|
|
7015
|
-
}
|
|
7016
|
-
}
|
|
7017
|
-
return [];
|
|
7018
|
-
}
|
|
7019
|
-
function createStorageClient(name, transport) {
|
|
7020
|
-
const rp = (r) => `/${r.replace(/^\/+|\/+$/g, "")}`;
|
|
7021
|
-
const ep = (r, id) => `${rp(r)}/${encodeURIComponent(String(id))}`;
|
|
7022
|
-
return {
|
|
7023
|
-
name,
|
|
7024
|
-
baseUrl: transport.baseUrl,
|
|
7025
|
-
transport,
|
|
7026
|
-
async list(resource, query) {
|
|
7027
|
-
const raw = await transport.get(rp(resource), { query });
|
|
7028
|
-
return { items: extractItems(raw, [resource]), raw };
|
|
7029
|
-
},
|
|
7030
|
-
async get(resource, id) {
|
|
7031
|
-
try {
|
|
7032
|
-
return await transport.get(ep(resource, id));
|
|
7033
|
-
} catch (error2) {
|
|
7034
|
-
if (error2 instanceof HasnaHttpError && error2.status === 404)
|
|
7035
|
-
return null;
|
|
7036
|
-
throw error2;
|
|
7037
|
-
}
|
|
7038
|
-
},
|
|
7039
|
-
async create(resource, body, idempotencyKey) {
|
|
7040
|
-
return transport.post(rp(resource), body, { idempotencyKey: idempotencyKey ?? newIdempotencyKey() });
|
|
7041
|
-
},
|
|
7042
|
-
async update(resource, id, patch, method = "PATCH") {
|
|
7043
|
-
const call = method === "PUT" ? transport.put : transport.patch;
|
|
7044
|
-
return call(ep(resource, id), patch);
|
|
7045
|
-
},
|
|
7046
|
-
async delete(resource, id) {
|
|
7047
|
-
try {
|
|
7048
|
-
await transport.del(ep(resource, id));
|
|
7049
|
-
} catch (error2) {
|
|
7050
|
-
if (error2 instanceof HasnaHttpError && error2.status === 404)
|
|
7051
|
-
return;
|
|
7052
|
-
throw error2;
|
|
7053
|
-
}
|
|
7054
|
-
}
|
|
7055
|
-
};
|
|
7056
|
-
}
|
|
7057
|
-
function resolveStorageClient(name, env = process.env, fetchImpl) {
|
|
6900
|
+
function resolveStoreClient(name, env = process.env) {
|
|
7058
6901
|
const resolution = resolveTransport(name, env);
|
|
7059
6902
|
if (resolution.misconfigured) {
|
|
6903
|
+
const wired2 = createClientTransport(name, env);
|
|
6904
|
+
if (wired2.transport === "http") {
|
|
6905
|
+
return {
|
|
6906
|
+
transport: "http",
|
|
6907
|
+
client: createHasnaStorageClient(name, wired2.client),
|
|
6908
|
+
resolution: {
|
|
6909
|
+
transport: "http",
|
|
6910
|
+
requested: "http",
|
|
6911
|
+
modeSource: resolution.modeSource === "default" ? "auto:api-url+seam-credential" : resolution.modeSource,
|
|
6912
|
+
baseUrl: wired2.resolution.baseUrl,
|
|
6913
|
+
apiKeyPresent: true,
|
|
6914
|
+
misconfigured: false,
|
|
6915
|
+
warning: null
|
|
6916
|
+
}
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
7060
6919
|
throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for the /v1 API.`);
|
|
7061
6920
|
}
|
|
7062
6921
|
if (resolution.transport === "sqlite" || !resolution.baseUrl) {
|
|
7063
6922
|
return { transport: "sqlite", client: null, resolution };
|
|
7064
6923
|
}
|
|
7065
|
-
const
|
|
7066
|
-
|
|
7067
|
-
if (!apiKey)
|
|
6924
|
+
const wired = createClientTransport(name, env);
|
|
6925
|
+
if (wired.transport !== "http") {
|
|
7068
6926
|
throw new Error(`Client for '${name}' resolved to the /v1 API without an API key.`);
|
|
7069
|
-
|
|
7070
|
-
return { transport: "http", client:
|
|
6927
|
+
}
|
|
6928
|
+
return { transport: "http", client: createHasnaStorageClient(name, wired.client), resolution };
|
|
7071
6929
|
}
|
|
7072
|
-
var
|
|
6930
|
+
var RETRY_STATUSES, IDEMPOTENT;
|
|
7073
6931
|
var init_client = __esm(() => {
|
|
7074
|
-
HasnaHttpError = class HasnaHttpError extends Error {
|
|
7075
|
-
status;
|
|
7076
|
-
method;
|
|
7077
|
-
path;
|
|
7078
|
-
body;
|
|
7079
|
-
constructor(method, path, status, body) {
|
|
7080
|
-
super(`Hasna request failed: ${method} ${path} -> ${status}`);
|
|
7081
|
-
this.name = "HasnaHttpError";
|
|
7082
|
-
this.status = status;
|
|
7083
|
-
this.method = method;
|
|
7084
|
-
this.path = path;
|
|
7085
|
-
this.body = body;
|
|
7086
|
-
}
|
|
7087
|
-
};
|
|
7088
6932
|
RETRY_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
7089
6933
|
IDEMPOTENT = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
7090
6934
|
});
|
|
@@ -7116,6 +6960,22 @@ function unwrap(res, key) {
|
|
|
7116
6960
|
}
|
|
7117
6961
|
return res;
|
|
7118
6962
|
}
|
|
6963
|
+
async function listResource(client, resource, query) {
|
|
6964
|
+
const raw = await client.transport.get(`/${resource}`, query ? { query } : undefined);
|
|
6965
|
+
return { items: extractEnvelopeItems(raw, resource), raw };
|
|
6966
|
+
}
|
|
6967
|
+
function extractEnvelopeItems(raw, resource) {
|
|
6968
|
+
if (Array.isArray(raw))
|
|
6969
|
+
return raw;
|
|
6970
|
+
if (raw && typeof raw === "object") {
|
|
6971
|
+
const obj = raw;
|
|
6972
|
+
for (const key of [resource, "items", "data", "results", "rows", "records"]) {
|
|
6973
|
+
if (Array.isArray(obj[key]))
|
|
6974
|
+
return obj[key];
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
return [];
|
|
6978
|
+
}
|
|
7119
6979
|
function apiStore(client) {
|
|
7120
6980
|
return {
|
|
7121
6981
|
mode: "http",
|
|
@@ -7123,7 +6983,7 @@ function apiStore(client) {
|
|
|
7123
6983
|
async createRecording(input, idempotencyKey) {
|
|
7124
6984
|
const keyCandidate = idempotencyKey === undefined && (input.id === undefined || input.id === null) ? randomUUID2() : idempotencyKey;
|
|
7125
6985
|
const identity = recordingCreateIdentity(input, keyCandidate, { bindIdempotencyKeyToId: false });
|
|
7126
|
-
const res = await client.create("recordings", identity.input, identity.idempotencyKey);
|
|
6986
|
+
const res = await client.create("recordings", identity.input, { idempotencyKey: identity.idempotencyKey });
|
|
7127
6987
|
return unwrap(res, "recording");
|
|
7128
6988
|
},
|
|
7129
6989
|
async getRecording(id) {
|
|
@@ -7131,7 +6991,7 @@ function apiStore(client) {
|
|
|
7131
6991
|
return res ? unwrap(res, "recording") : null;
|
|
7132
6992
|
},
|
|
7133
6993
|
async listRecordings(filter) {
|
|
7134
|
-
const { items } = await client
|
|
6994
|
+
const { items } = await listResource(client, "recordings", listQuery(filter));
|
|
7135
6995
|
return items;
|
|
7136
6996
|
},
|
|
7137
6997
|
async countRecordings(filter) {
|
|
@@ -7142,7 +7002,7 @@ function apiStore(client) {
|
|
|
7142
7002
|
const seenPageKeys = new Set;
|
|
7143
7003
|
while (pageRequests < maxPageRequests) {
|
|
7144
7004
|
pageRequests += 1;
|
|
7145
|
-
const { items, raw } = await client
|
|
7005
|
+
const { items, raw } = await listResource(client, "recordings", {
|
|
7146
7006
|
...listQuery(filter),
|
|
7147
7007
|
limit: pageLimit,
|
|
7148
7008
|
offset
|
|
@@ -7165,7 +7025,7 @@ function apiStore(client) {
|
|
|
7165
7025
|
throw new Error(`Recordings API exceeded ${maxPageRequests} pages while counting legacy results`);
|
|
7166
7026
|
},
|
|
7167
7027
|
async searchRecordings(query, filter) {
|
|
7168
|
-
const { items } = await client
|
|
7028
|
+
const { items } = await listResource(client, "recordings", listQuery({ ...filter ?? {}, search: query }));
|
|
7169
7029
|
return items;
|
|
7170
7030
|
},
|
|
7171
7031
|
async deleteRecording(id) {
|
|
@@ -7197,7 +7057,7 @@ function apiStore(client) {
|
|
|
7197
7057
|
return res ? unwrap(res, "agent") : null;
|
|
7198
7058
|
},
|
|
7199
7059
|
async listAgents() {
|
|
7200
|
-
const { items } = await client
|
|
7060
|
+
const { items } = await listResource(client, "agents");
|
|
7201
7061
|
return items;
|
|
7202
7062
|
},
|
|
7203
7063
|
async heartbeatAgent(idOrName) {
|
|
@@ -7237,7 +7097,7 @@ function apiStore(client) {
|
|
|
7237
7097
|
return res ? unwrap(res, "project") : null;
|
|
7238
7098
|
},
|
|
7239
7099
|
async listProjects() {
|
|
7240
|
-
const { items } = await client
|
|
7100
|
+
const { items } = await listResource(client, "projects");
|
|
7241
7101
|
return items;
|
|
7242
7102
|
},
|
|
7243
7103
|
async saveFeedback(input) {
|
|
@@ -7284,7 +7144,7 @@ async function countStoreRecordings(store, filter) {
|
|
|
7284
7144
|
function getStore(env = process.env) {
|
|
7285
7145
|
if (env === process.env && cached)
|
|
7286
7146
|
return cached;
|
|
7287
|
-
const resolved =
|
|
7147
|
+
const resolved = resolveStoreClient(APP, env);
|
|
7288
7148
|
const store = resolved.transport === "http" ? apiStore(resolved.client) : localStore;
|
|
7289
7149
|
if (env === process.env)
|
|
7290
7150
|
cached = store;
|