@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.
@@ -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.8",
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.3",
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 appendQuery(path, query) {
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 keys = envKeys(name);
7066
- const apiKey = firstEnv(env, keys.apiKeyKeys)?.value;
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
- const transport = createHttpTransport({ name, baseUrl: resolution.baseUrl, apiKey, ...fetchImpl ? { fetchImpl } : {} });
7070
- return { transport: "http", client: createStorageClient(name, transport), resolution };
6927
+ }
6928
+ return { transport: "http", client: createHasnaStorageClient(name, wired.client), resolution };
7071
6929
  }
7072
- var HasnaHttpError, RETRY_STATUSES, IDEMPOTENT, defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
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.list("recordings", listQuery(filter));
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.list("recordings", {
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.list("recordings", listQuery({ ...filter ?? {}, search: query }));
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.list("agents");
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.list("projects");
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 = resolveStorageClient(APP, env);
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;