@hasna/recordings 0.1.37 → 0.2.0

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.
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  var __defProp = Object.defineProperty;
4
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
5
4
  var __returnValue = (v) => v;
6
5
  function __exportSetter(name, newValue) {
7
6
  this[name] = __returnValue.bind(null, newValue);
@@ -19,7 +18,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
18
  var __require = import.meta.require;
20
19
 
21
20
  // src/version.ts
22
- var VERSION = "0.1.37";
21
+ var VERSION = "0.2.0";
23
22
 
24
23
  // src/db/remote-storage.ts
25
24
  import pg from "pg";
@@ -436,6 +435,20 @@ async function listAgents(pg2) {
436
435
  const rows = await pg2.all("SELECT * FROM agents ORDER BY last_seen_at DESC");
437
436
  return rows.map(parseAgent);
438
437
  }
438
+ async function heartbeatAgent(pg2, idOrName) {
439
+ const agent = await getAgent(pg2, idOrName);
440
+ if (!agent)
441
+ return null;
442
+ await pg2.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", new Date().toISOString(), agent.id);
443
+ return getAgent(pg2, agent.id);
444
+ }
445
+ async function setAgentFocus(pg2, idOrName, projectId) {
446
+ const agent = await getAgent(pg2, idOrName);
447
+ if (!agent)
448
+ return null;
449
+ await pg2.run("UPDATE agents SET active_project_id = ?, last_seen_at = ? WHERE id = ?", projectId, new Date().toISOString(), agent.id);
450
+ return getAgent(pg2, agent.id);
451
+ }
439
452
  async function registerProject(pg2, name, path, description) {
440
453
  if (!name || !path)
441
454
  throw new Error("name and path are required");
@@ -460,6 +473,13 @@ async function listProjects(pg2) {
460
473
  const rows = await pg2.all("SELECT * FROM projects ORDER BY updated_at DESC");
461
474
  return rows.map(parseProject);
462
475
  }
476
+ async function saveFeedback(pg2, input) {
477
+ if (typeof input.message !== "string" || !input.message.trim()) {
478
+ throw new Error("message is required");
479
+ }
480
+ await pg2.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", input.message, input.email || null, input.category || "general", input.version || null);
481
+ return { saved: true };
482
+ }
463
483
 
464
484
  // src/server/v1.ts
465
485
  function json(body, status = 200) {
@@ -504,6 +524,7 @@ async function handleV1Request(req, url) {
504
524
  const segments = path.split("/").filter(Boolean);
505
525
  const resource = segments[1];
506
526
  const id = segments[2] ? decodeURIComponent(segments[2]) : undefined;
527
+ const action = segments[3] ? decodeURIComponent(segments[3]) : undefined;
507
528
  try {
508
529
  if (resource === "recordings") {
509
530
  if (!id) {
@@ -561,6 +582,21 @@ async function handleV1Request(req, url) {
561
582
  }
562
583
  return error(405, `method ${method} not allowed on /v1/agents`);
563
584
  }
585
+ if (action === "heartbeat") {
586
+ if (method !== "POST")
587
+ return error(405, `method ${method} not allowed on /v1/agents/:id/heartbeat`);
588
+ const agent = await heartbeatAgent(pg2, id);
589
+ return agent ? json({ agent }) : error(404, "agent not found");
590
+ }
591
+ if (action === "focus") {
592
+ if (method !== "POST")
593
+ return error(405, `method ${method} not allowed on /v1/agents/:id/focus`);
594
+ const body = await readJson(req);
595
+ const agent = await setAgentFocus(pg2, id, body?.project_id ?? null);
596
+ return agent ? json({ agent }) : error(404, "agent not found");
597
+ }
598
+ if (action)
599
+ return error(404, `unknown agent action: ${action}`);
564
600
  if (method === "GET") {
565
601
  const agent = await getAgent(pg2, id);
566
602
  return agent ? json({ agent }) : error(404, "agent not found");
@@ -589,6 +625,23 @@ async function handleV1Request(req, url) {
589
625
  }
590
626
  return error(405, `method ${method} not allowed on /v1/projects/:id`);
591
627
  }
628
+ if (resource === "feedback") {
629
+ if (id)
630
+ return error(404, "feedback has no item routes");
631
+ if (method === "POST") {
632
+ const body = await readJson(req);
633
+ if (!body || typeof body.message !== "string" || !body.message.trim()) {
634
+ return error(400, "message is required");
635
+ }
636
+ return json(await saveFeedback(pg2, {
637
+ message: body.message,
638
+ email: body.email ?? null,
639
+ category: body.category ?? null,
640
+ version: body.version ?? null
641
+ }), 201);
642
+ }
643
+ return error(405, `method ${method} not allowed on /v1/feedback`);
644
+ }
592
645
  return error(404, `unknown /v1 resource: ${resource ?? ""}`);
593
646
  } catch (e) {
594
647
  return error(500, e.message);
@@ -5265,15 +5318,6 @@ function ensureColumn(db, table, column, definition) {
5265
5318
  }
5266
5319
  db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
5267
5320
  }
5268
- function getAdapter() {
5269
- if (!_adapter) {
5270
- getDatabase();
5271
- }
5272
- return _adapter;
5273
- }
5274
- function getDbPath() {
5275
- return loadConfig().db_path;
5276
- }
5277
5321
  function shortUuid2() {
5278
5322
  return crypto.randomUUID().slice(0, 8);
5279
5323
  }
@@ -5527,7 +5571,7 @@ function listAgents2(db) {
5527
5571
  const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
5528
5572
  return rows.map(parseAgent2);
5529
5573
  }
5530
- function heartbeatAgent(idOrName, db) {
5574
+ function heartbeatAgent2(idOrName, db) {
5531
5575
  const d = db || getDatabase();
5532
5576
  const agent = getAgent2(idOrName, d);
5533
5577
  if (!agent)
@@ -5535,7 +5579,7 @@ function heartbeatAgent(idOrName, db) {
5535
5579
  d.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(new Date().toISOString(), agent.id);
5536
5580
  return getAgent2(agent.id, d);
5537
5581
  }
5538
- function setAgentFocus(idOrName, projectId, db) {
5582
+ function setAgentFocus2(idOrName, projectId, db) {
5539
5583
  const d = db || getDatabase();
5540
5584
  const agent = getAgent2(idOrName, d);
5541
5585
  if (!agent)
@@ -5587,6 +5631,478 @@ var init_projects = __esm(() => {
5587
5631
  init_database();
5588
5632
  });
5589
5633
 
5634
+ // src/db/feedback.ts
5635
+ function saveFeedback2(input) {
5636
+ const db = getDatabase();
5637
+ db.query("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(input.message, input.email ?? null, input.category ?? "general", input.version ?? VERSION);
5638
+ }
5639
+ var init_feedback = __esm(() => {
5640
+ init_database();
5641
+ });
5642
+
5643
+ // src/http/client.ts
5644
+ function envToken(name) {
5645
+ return name.toUpperCase().replace(/-/g, "_");
5646
+ }
5647
+ function normalizeMode(value) {
5648
+ const normalized = value.trim().toLowerCase().replace(/-/g, "_");
5649
+ if (normalized === "local")
5650
+ return { mode: "local", deprecatedAlias: null };
5651
+ if (normalized === "cloud")
5652
+ return { mode: "cloud", deprecatedAlias: null };
5653
+ if (DEPRECATED_MODE_ALIASES.includes(normalized)) {
5654
+ return { mode: "cloud", deprecatedAlias: normalized };
5655
+ }
5656
+ throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
5657
+ }
5658
+ function defaultCloudBaseUrl(name) {
5659
+ return `https://${name}.hasna.xyz`;
5660
+ }
5661
+ function envKeys(name) {
5662
+ const token = envToken(name);
5663
+ return {
5664
+ modeKeys: [`HASNA_${token}_STORAGE_MODE`, `HASNA_${token}_MODE`, `${token}_STORAGE_MODE`, `${token}_MODE`],
5665
+ apiUrlKeys: [`HASNA_${token}_API_URL`, `${token}_API_URL`],
5666
+ apiKeyKeys: [`HASNA_${token}_API_KEY`, `${token}_API_KEY`]
5667
+ };
5668
+ }
5669
+ function firstEnv(env, keys) {
5670
+ for (const key of keys) {
5671
+ const value = env[key]?.trim();
5672
+ if (value)
5673
+ return { key, value };
5674
+ }
5675
+ return null;
5676
+ }
5677
+ function toV1BaseUrl(apiUrl) {
5678
+ const url = new URL(apiUrl);
5679
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
5680
+ throw new Error("API URL must use http or https.");
5681
+ }
5682
+ let path = url.pathname.replace(/\/+$/, "");
5683
+ if (path.endsWith("/v1"))
5684
+ path = path.slice(0, -"/v1".length);
5685
+ url.pathname = `${path}/v1`;
5686
+ url.search = "";
5687
+ url.hash = "";
5688
+ return url.toString().replace(/\/+$/, "");
5689
+ }
5690
+ function resolveTransport(name, env = process.env) {
5691
+ const keys = envKeys(name);
5692
+ const modeHit = firstEnv(env, keys.modeKeys);
5693
+ const urlHit = firstEnv(env, keys.apiUrlKeys);
5694
+ const keyHit = firstEnv(env, keys.apiKeyKeys);
5695
+ let mode = "local";
5696
+ let deprecatedAlias = null;
5697
+ let modeSource = "default";
5698
+ if (modeHit) {
5699
+ const normalized = normalizeMode(modeHit.value);
5700
+ mode = normalized.mode;
5701
+ deprecatedAlias = normalized.deprecatedAlias;
5702
+ modeSource = modeHit.key;
5703
+ } else if (urlHit && keyHit) {
5704
+ mode = "cloud";
5705
+ modeSource = "auto:api-url+api-key";
5706
+ }
5707
+ if (mode === "local") {
5708
+ return { transport: "local", mode, deprecatedAlias, modeSource, baseUrl: null, apiKeyPresent: Boolean(keyHit), misconfigured: false, warning: null };
5709
+ }
5710
+ if (!keyHit) {
5711
+ return {
5712
+ transport: "local",
5713
+ mode,
5714
+ deprecatedAlias,
5715
+ modeSource,
5716
+ baseUrl: null,
5717
+ apiKeyPresent: false,
5718
+ misconfigured: true,
5719
+ warning: `${modeSource}=cloud but no API key is set (${keys.apiKeyKeys[0]}). Refusing to route to cloud.`
5720
+ };
5721
+ }
5722
+ const rawUrl = urlHit?.value ?? defaultCloudBaseUrl(name);
5723
+ let baseUrl;
5724
+ try {
5725
+ baseUrl = toV1BaseUrl(rawUrl);
5726
+ } catch (error2) {
5727
+ const message = error2 instanceof Error ? error2.message : String(error2);
5728
+ return { transport: "local", mode, deprecatedAlias, modeSource, baseUrl: null, apiKeyPresent: true, misconfigured: true, warning: `Invalid API URL: ${message}.` };
5729
+ }
5730
+ return { transport: "cloud-http", mode, deprecatedAlias, modeSource, baseUrl, apiKeyPresent: true, misconfigured: false, warning: null };
5731
+ }
5732
+ function appendQuery(path, query) {
5733
+ if (!query)
5734
+ return path;
5735
+ const params = new URLSearchParams;
5736
+ for (const [key, value] of Object.entries(query)) {
5737
+ if (value === null || value === undefined)
5738
+ continue;
5739
+ if (Array.isArray(value))
5740
+ for (const v of value)
5741
+ params.append(key, String(v));
5742
+ else
5743
+ params.append(key, String(value));
5744
+ }
5745
+ const qs = params.toString();
5746
+ return qs ? `${path}${path.includes("?") ? "&" : "?"}${qs}` : path;
5747
+ }
5748
+ function createHttpTransport(options) {
5749
+ const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
5750
+ const base = options.baseUrl.replace(/\/+$/, "");
5751
+ const timeoutMs = options.timeoutMs ?? 30000;
5752
+ const sleep = options.sleepImpl ?? defaultSleep;
5753
+ async function once(method, rel, url, body, opts) {
5754
+ const headers = {
5755
+ "x-api-key": options.apiKey,
5756
+ Authorization: `Bearer ${options.apiKey}`,
5757
+ Accept: "application/json",
5758
+ ...opts.headers ?? {}
5759
+ };
5760
+ if (opts.idempotencyKey)
5761
+ headers["Idempotency-Key"] = opts.idempotencyKey;
5762
+ const init = { method, headers };
5763
+ if (body !== undefined) {
5764
+ headers["Content-Type"] = "application/json";
5765
+ init.body = JSON.stringify(body);
5766
+ }
5767
+ const controller = new AbortController;
5768
+ const onAbort = () => controller.abort();
5769
+ if (opts.signal) {
5770
+ if (opts.signal.aborted)
5771
+ controller.abort();
5772
+ else
5773
+ opts.signal.addEventListener("abort", onAbort, { once: true });
5774
+ }
5775
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
5776
+ init.signal = controller.signal;
5777
+ let response;
5778
+ try {
5779
+ response = await fetchImpl(url, init);
5780
+ } catch (error2) {
5781
+ const err = error2 instanceof Error ? error2 : new Error(String(error2));
5782
+ if (opts.signal?.aborted)
5783
+ return { ok: false, retryable: false, error: err };
5784
+ return { ok: false, retryable: true, error: err };
5785
+ } finally {
5786
+ clearTimeout(timer);
5787
+ if (opts.signal)
5788
+ opts.signal.removeEventListener("abort", onAbort);
5789
+ }
5790
+ const text = await response.text();
5791
+ let parsed = undefined;
5792
+ if (text.length > 0) {
5793
+ try {
5794
+ parsed = JSON.parse(text);
5795
+ } catch {
5796
+ parsed = text;
5797
+ }
5798
+ }
5799
+ if (!response.ok) {
5800
+ return { ok: false, retryable: RETRY_STATUSES.has(response.status), error: new HasnaHttpError(method, rel, response.status, parsed) };
5801
+ }
5802
+ return { ok: true, value: parsed };
5803
+ }
5804
+ async function request(method, path, body, opts = {}) {
5805
+ const upper = method.toUpperCase();
5806
+ const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
5807
+ const url = `${base}${rel}`;
5808
+ const methodRetryable = IDEMPOTENT.has(upper) || Boolean(opts.idempotencyKey);
5809
+ const maxRetries = opts.retries ?? 2;
5810
+ const maxAttempts = methodRetryable ? maxRetries + 1 : 1;
5811
+ let last = null;
5812
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
5813
+ const result = await once(upper, rel, url, body, opts);
5814
+ if (result.ok)
5815
+ return result.value;
5816
+ last = result;
5817
+ const canRetry = methodRetryable && result.retryable && attempt < maxAttempts;
5818
+ if (!canRetry)
5819
+ break;
5820
+ const backoff = Math.min(2000, 200 * 2 ** (attempt - 1));
5821
+ const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
5822
+ await sleep(backoff + jitter);
5823
+ }
5824
+ throw last.error;
5825
+ }
5826
+ return {
5827
+ baseUrl: base,
5828
+ request,
5829
+ get: (path, opts) => request("GET", path, undefined, opts),
5830
+ post: (path, body, opts) => request("POST", path, body, opts),
5831
+ patch: (path, body, opts) => request("PATCH", path, body, opts),
5832
+ put: (path, body, opts) => request("PUT", path, body, opts),
5833
+ del: (path, body, opts) => request("DELETE", path, body, opts)
5834
+ };
5835
+ }
5836
+ function newIdempotencyKey() {
5837
+ const g = globalThis;
5838
+ if (g.crypto?.randomUUID)
5839
+ return g.crypto.randomUUID();
5840
+ return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
5841
+ }
5842
+ function extractItems(raw, extraKeys = []) {
5843
+ if (Array.isArray(raw))
5844
+ return raw;
5845
+ if (raw && typeof raw === "object") {
5846
+ const obj = raw;
5847
+ for (const key of [...extraKeys, "items", "data", "results", "rows", "records"]) {
5848
+ if (Array.isArray(obj[key]))
5849
+ return obj[key];
5850
+ }
5851
+ }
5852
+ return [];
5853
+ }
5854
+ function createStorageClient(name, transport) {
5855
+ const rp = (r) => `/${r.replace(/^\/+|\/+$/g, "")}`;
5856
+ const ep = (r, id) => `${rp(r)}/${encodeURIComponent(String(id))}`;
5857
+ return {
5858
+ name,
5859
+ baseUrl: transport.baseUrl,
5860
+ transport,
5861
+ async list(resource, query) {
5862
+ const raw = await transport.get(rp(resource), { query });
5863
+ return { items: extractItems(raw, [resource]), raw };
5864
+ },
5865
+ async get(resource, id) {
5866
+ try {
5867
+ return await transport.get(ep(resource, id));
5868
+ } catch (error2) {
5869
+ if (error2 instanceof HasnaHttpError && error2.status === 404)
5870
+ return null;
5871
+ throw error2;
5872
+ }
5873
+ },
5874
+ async create(resource, body, idempotencyKey) {
5875
+ return transport.post(rp(resource), body, { idempotencyKey: idempotencyKey ?? newIdempotencyKey() });
5876
+ },
5877
+ async update(resource, id, patch, method = "PATCH") {
5878
+ const call = method === "PUT" ? transport.put : transport.patch;
5879
+ return call(ep(resource, id), patch);
5880
+ },
5881
+ async delete(resource, id) {
5882
+ try {
5883
+ await transport.del(ep(resource, id));
5884
+ } catch (error2) {
5885
+ if (error2 instanceof HasnaHttpError && error2.status === 404)
5886
+ return;
5887
+ throw error2;
5888
+ }
5889
+ }
5890
+ };
5891
+ }
5892
+ function resolveStorageClient(name, env = process.env, fetchImpl) {
5893
+ const resolution = resolveTransport(name, env);
5894
+ if (resolution.misconfigured) {
5895
+ throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for cloud mode.`);
5896
+ }
5897
+ if (resolution.transport === "local" || !resolution.baseUrl) {
5898
+ return { transport: "local", client: null, resolution };
5899
+ }
5900
+ const keys = envKeys(name);
5901
+ const apiKey = firstEnv(env, keys.apiKeyKeys)?.value;
5902
+ if (!apiKey)
5903
+ throw new Error(`Client for '${name}' resolved to cloud-http without an API key.`);
5904
+ const transport = createHttpTransport({ name, baseUrl: resolution.baseUrl, apiKey, ...fetchImpl ? { fetchImpl } : {} });
5905
+ return { transport: "cloud-http", client: createStorageClient(name, transport), resolution };
5906
+ }
5907
+ var DEPRECATED_MODE_ALIASES, HasnaHttpError, RETRY_STATUSES, IDEMPOTENT, defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
5908
+ var init_client = __esm(() => {
5909
+ DEPRECATED_MODE_ALIASES = ["self_hosted", "remote", "hybrid"];
5910
+ HasnaHttpError = class HasnaHttpError extends Error {
5911
+ status;
5912
+ method;
5913
+ path;
5914
+ body;
5915
+ constructor(method, path, status, body) {
5916
+ super(`Hasna request failed: ${method} ${path} -> ${status}`);
5917
+ this.name = "HasnaHttpError";
5918
+ this.status = status;
5919
+ this.method = method;
5920
+ this.path = path;
5921
+ this.body = body;
5922
+ }
5923
+ };
5924
+ RETRY_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);
5925
+ IDEMPOTENT = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
5926
+ });
5927
+
5928
+ // src/store.ts
5929
+ function listQuery(filter) {
5930
+ if (!filter)
5931
+ return {};
5932
+ return {
5933
+ agent_id: filter.agent_id,
5934
+ project_id: filter.project_id,
5935
+ session_id: filter.session_id,
5936
+ processing_mode: filter.processing_mode,
5937
+ search: filter.search,
5938
+ since: filter.since,
5939
+ until: filter.until,
5940
+ limit: filter.limit,
5941
+ offset: filter.offset
5942
+ };
5943
+ }
5944
+ function unwrap(res, key) {
5945
+ if (res && typeof res === "object" && key in res) {
5946
+ return res[key];
5947
+ }
5948
+ return res;
5949
+ }
5950
+ function apiStore(client) {
5951
+ return {
5952
+ mode: "cloud-http",
5953
+ baseUrl: client.baseUrl,
5954
+ async createRecording(input) {
5955
+ const res = await client.create("recordings", input);
5956
+ return unwrap(res, "recording");
5957
+ },
5958
+ async getRecording(id) {
5959
+ const res = await client.get("recordings", id);
5960
+ return res ? unwrap(res, "recording") : null;
5961
+ },
5962
+ async listRecordings(filter) {
5963
+ const { items } = await client.list("recordings", listQuery(filter));
5964
+ return items;
5965
+ },
5966
+ async searchRecordings(query, filter) {
5967
+ const { items } = await client.list("recordings", listQuery({ ...filter ?? {}, search: query }));
5968
+ return items;
5969
+ },
5970
+ async deleteRecording(id) {
5971
+ try {
5972
+ const res = await client.transport.del(`/recordings/${encodeURIComponent(id)}`);
5973
+ return res?.deleted !== false;
5974
+ } catch (error2) {
5975
+ if (error2 && typeof error2 === "object" && error2.status === 404)
5976
+ return false;
5977
+ throw error2;
5978
+ }
5979
+ },
5980
+ async getRecordingStats() {
5981
+ const res = await client.transport.get("/stats");
5982
+ return {
5983
+ total: res.total ?? 0,
5984
+ raw: res.raw ?? 0,
5985
+ enhanced: res.enhanced ?? 0,
5986
+ total_duration_ms: res.total_duration_ms ?? 0,
5987
+ by_model: res.by_model ?? {}
5988
+ };
5989
+ },
5990
+ async registerAgent(name, description, role) {
5991
+ const res = await client.create("agents", { name, description, role });
5992
+ return unwrap(res, "agent");
5993
+ },
5994
+ async getAgent(idOrName) {
5995
+ const res = await client.get("agents", idOrName);
5996
+ return res ? unwrap(res, "agent") : null;
5997
+ },
5998
+ async listAgents() {
5999
+ const { items } = await client.list("agents");
6000
+ return items;
6001
+ },
6002
+ async heartbeatAgent(idOrName) {
6003
+ try {
6004
+ const res = await client.transport.post(`/agents/${encodeURIComponent(idOrName)}/heartbeat`);
6005
+ return res ? unwrap(res, "agent") : null;
6006
+ } catch (error2) {
6007
+ if (error2 && typeof error2 === "object" && error2.status === 404)
6008
+ return null;
6009
+ throw error2;
6010
+ }
6011
+ },
6012
+ async setAgentFocus(idOrName, projectId) {
6013
+ try {
6014
+ const res = await client.transport.post(`/agents/${encodeURIComponent(idOrName)}/focus`, { project_id: projectId });
6015
+ return res ? unwrap(res, "agent") : null;
6016
+ } catch (error2) {
6017
+ if (error2 && typeof error2 === "object" && error2.status === 404)
6018
+ return null;
6019
+ throw error2;
6020
+ }
6021
+ },
6022
+ async registerProject(name, path, description) {
6023
+ const res = await client.create("projects", { name, path, description });
6024
+ return unwrap(res, "project");
6025
+ },
6026
+ async getProject(idOrPath) {
6027
+ const res = await client.get("projects", idOrPath);
6028
+ return res ? unwrap(res, "project") : null;
6029
+ },
6030
+ async listProjects() {
6031
+ const { items } = await client.list("projects");
6032
+ return items;
6033
+ },
6034
+ async saveFeedback(input) {
6035
+ await client.create("feedback", input);
6036
+ }
6037
+ };
6038
+ }
6039
+ function getStore(env = process.env) {
6040
+ if (env === process.env && cached)
6041
+ return cached;
6042
+ const resolved = resolveStorageClient(APP, env);
6043
+ const store = resolved.transport === "cloud-http" ? apiStore(resolved.client) : localStore;
6044
+ if (env === process.env)
6045
+ cached = store;
6046
+ return store;
6047
+ }
6048
+ var APP = "recordings", localStore, cached = null;
6049
+ var init_store = __esm(() => {
6050
+ init_recordings();
6051
+ init_agents();
6052
+ init_projects();
6053
+ init_feedback();
6054
+ init_client();
6055
+ localStore = {
6056
+ mode: "local",
6057
+ baseUrl: null,
6058
+ async createRecording(input) {
6059
+ return createRecording2(input);
6060
+ },
6061
+ async getRecording(id) {
6062
+ return getRecording2(id);
6063
+ },
6064
+ async listRecordings(filter) {
6065
+ return listRecordings2(filter);
6066
+ },
6067
+ async searchRecordings(query, filter) {
6068
+ return searchRecordings(query, filter);
6069
+ },
6070
+ async deleteRecording(id) {
6071
+ return deleteRecording2(id);
6072
+ },
6073
+ async getRecordingStats() {
6074
+ return getRecordingStats2();
6075
+ },
6076
+ async registerAgent(name, description, role) {
6077
+ return registerAgent2(name, description, role);
6078
+ },
6079
+ async getAgent(idOrName) {
6080
+ return getAgent2(idOrName);
6081
+ },
6082
+ async listAgents() {
6083
+ return listAgents2();
6084
+ },
6085
+ async heartbeatAgent(idOrName) {
6086
+ return heartbeatAgent2(idOrName);
6087
+ },
6088
+ async setAgentFocus(idOrName, projectId) {
6089
+ return setAgentFocus2(idOrName, projectId);
6090
+ },
6091
+ async registerProject(name, path, description) {
6092
+ return registerProject2(name, path, description);
6093
+ },
6094
+ async getProject(idOrPath) {
6095
+ return getProject2(idOrPath);
6096
+ },
6097
+ async listProjects() {
6098
+ return listProjects2();
6099
+ },
6100
+ async saveFeedback(input) {
6101
+ saveFeedback2(input);
6102
+ }
6103
+ };
6104
+ });
6105
+
5590
6106
  // src/types/index.ts
5591
6107
  var TranscriptionError, EnhancementError;
5592
6108
  var init_types2 = __esm(() => {
@@ -5794,404 +6310,6 @@ var init_enhancer = __esm(() => {
5794
6310
  init_transcriber();
5795
6311
  });
5796
6312
 
5797
- // src/db/storage-config.ts
5798
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5799
- import { homedir as homedir2 } from "os";
5800
- import { join as join2 } from "path";
5801
- function readEnv(name) {
5802
- const value = process.env[name]?.trim();
5803
- return value || undefined;
5804
- }
5805
- function normalizeMode(value) {
5806
- const normalized = value?.trim().toLowerCase();
5807
- if (normalized === "local" || normalized === "hybrid" || normalized === "remote")
5808
- return normalized;
5809
- return;
5810
- }
5811
- function getStorageDatabaseEnvName() {
5812
- for (const name of STORAGE_DATABASE_ENV) {
5813
- if (readEnv(name))
5814
- return name;
5815
- }
5816
- return null;
5817
- }
5818
- function getStorageDatabaseEnv() {
5819
- const name = getStorageDatabaseEnvName();
5820
- return name ? { name } : null;
5821
- }
5822
- function getStorageDatabaseUrl() {
5823
- const env = getStorageDatabaseEnv();
5824
- return env ? readEnv(env.name) : undefined;
5825
- }
5826
- function getStorageConfigPath() {
5827
- const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
5828
- if (override)
5829
- return override;
5830
- return join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
5831
- }
5832
- function getStorageConfig() {
5833
- const config = {
5834
- mode: "local",
5835
- postgres: {
5836
- host: "",
5837
- port: 5432,
5838
- username: "",
5839
- password_env: "RECORDINGS_DATABASE_PASSWORD",
5840
- ssl: true
5841
- }
5842
- };
5843
- const storageConfigPath = getStorageConfigPath();
5844
- if (existsSync2(storageConfigPath)) {
5845
- try {
5846
- const raw = JSON.parse(readFileSync2(storageConfigPath, "utf-8"));
5847
- const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
5848
- config.mode = normalizeMode(raw.mode) ?? config.mode;
5849
- config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
5850
- } catch {}
5851
- }
5852
- const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
5853
- const normalizedMode = normalizeMode(modeOverride);
5854
- if (normalizedMode) {
5855
- config.mode = normalizedMode;
5856
- } else if (getStorageDatabaseUrl() && config.mode === "local") {
5857
- config.mode = "hybrid";
5858
- }
5859
- return config;
5860
- }
5861
- function getStorageConnectionString(dbName = "recordings") {
5862
- const direct = getStorageDatabaseUrl();
5863
- if (direct)
5864
- return direct;
5865
- const config = getStorageConfig();
5866
- const { host, port, username, password_env, ssl } = config.postgres;
5867
- if (!host || !username) {
5868
- throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
5869
- }
5870
- const password = process.env[password_env];
5871
- if (!password) {
5872
- throw new Error(`Storage database password is not set. Export ${password_env}.`);
5873
- }
5874
- const sslParam = ssl ? "?sslmode=require" : "";
5875
- return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
5876
- }
5877
- var LEGACY_DATABASE_CONFIG_KEY, RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL", RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL", RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE", RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE", RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG", STORAGE_DATABASE_ENV;
5878
- var init_storage_config = __esm(() => {
5879
- LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
5880
- STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
5881
- });
5882
-
5883
- // src/db/storage-sync.ts
5884
- function quoteId(value) {
5885
- return `"${value.replace(/"/g, '""')}"`;
5886
- }
5887
- async function getRemoteColumns(remote, table) {
5888
- const rows = await remote.all(`SELECT column_name FROM information_schema.columns
5889
- WHERE table_schema = 'public' AND table_name = $1`, table);
5890
- return new Set(rows.map((row) => row.column_name));
5891
- }
5892
- async function upsertPg(remote, table, rows) {
5893
- if (rows.length === 0)
5894
- return 0;
5895
- const keyColumns = TABLE_KEYS[table] ?? ["id"];
5896
- const remoteColumns = await getRemoteColumns(remote, table);
5897
- let written = 0;
5898
- for (const row of rows) {
5899
- const columns = Object.keys(row).filter((column) => remoteColumns.has(column));
5900
- if (keyColumns.some((column) => !columns.includes(column)))
5901
- continue;
5902
- const values = columns.map((column) => row[column]);
5903
- const placeholders = columns.map((_, index) => `$${index + 1}`).join(", ");
5904
- const updateColumns = columns.filter((column) => !keyColumns.includes(column));
5905
- const updateClause = updateColumns.length > 0 ? `DO UPDATE SET ${updateColumns.map((column) => `${quoteId(column)} = EXCLUDED.${quoteId(column)}`).join(", ")}` : "DO NOTHING";
5906
- await remote.run(`INSERT INTO ${quoteId(table)} (${columns.map(quoteId).join(", ")})
5907
- VALUES (${placeholders})
5908
- ON CONFLICT (${keyColumns.map(quoteId).join(", ")}) ${updateClause}`, ...values);
5909
- written++;
5910
- }
5911
- return written;
5912
- }
5913
- function upsertSqlite(db, table, rows) {
5914
- const keyColumns = TABLE_KEYS[table] ?? ["id"];
5915
- let written = 0;
5916
- for (const row of rows) {
5917
- const columns = Object.keys(row);
5918
- if (keyColumns.some((column) => !columns.includes(column)))
5919
- continue;
5920
- const updateColumns = columns.filter((column) => !keyColumns.includes(column));
5921
- const updateClause = updateColumns.length > 0 ? `DO UPDATE SET ${updateColumns.map((column) => `${quoteId(column)} = excluded.${quoteId(column)}`).join(", ")}` : "DO NOTHING";
5922
- db.query(`INSERT INTO ${quoteId(table)} (${columns.map(quoteId).join(", ")})
5923
- VALUES (${columns.map(() => "?").join(", ")})
5924
- ON CONFLICT(${keyColumns.map(quoteId).join(", ")}) ${updateClause}`).run(...columns.map((column) => row[column]));
5925
- written++;
5926
- }
5927
- return written;
5928
- }
5929
- async function getStoragePg() {
5930
- return new PgAdapterAsync(getStorageConnectionString("recordings"));
5931
- }
5932
- async function runStorageMigrations(remote) {
5933
- for (const migration of PG_MIGRATIONS) {
5934
- await remote.exec(migration);
5935
- }
5936
- }
5937
- function getStorageStatus(db = getDatabase()) {
5938
- const config = getStorageConfig();
5939
- const activeEnv = getStorageDatabaseEnv();
5940
- const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
5941
- return {
5942
- configured: Boolean(activeEnv) || hasConfiguredPostgres,
5943
- mode: config.mode,
5944
- enabled: config.mode === "hybrid" || config.mode === "remote",
5945
- env: STORAGE_DATABASE_ENV,
5946
- activeEnv: activeEnv?.name ?? null,
5947
- service: "recordings",
5948
- db_path: getDbPath(),
5949
- tables: STORAGE_TABLES.map((table) => {
5950
- try {
5951
- const row = db.query(`SELECT COUNT(*) as count FROM ${quoteId(table)}`).get();
5952
- return { table, rows: row.count };
5953
- } catch {
5954
- return { table, rows: 0 };
5955
- }
5956
- })
5957
- };
5958
- }
5959
- async function pushStorageChanges(tables = [...STORAGE_TABLES]) {
5960
- const db = getDatabase();
5961
- const remote = await getStoragePg();
5962
- const results = [];
5963
- try {
5964
- await runStorageMigrations(remote);
5965
- for (const table of tables) {
5966
- const result = { table, direction: "push", rows_read: 0, rows_written: 0, errors: [] };
5967
- try {
5968
- const rows = db.query(`SELECT * FROM ${quoteId(table)}`).all();
5969
- result.rows_read = rows.length;
5970
- result.rows_written = await upsertPg(remote, table, rows);
5971
- } catch (error2) {
5972
- result.errors.push(error2 instanceof Error ? error2.message : String(error2));
5973
- }
5974
- results.push(result);
5975
- }
5976
- } finally {
5977
- await remote.close();
5978
- }
5979
- return results;
5980
- }
5981
- async function pullStorageChanges(tables = [...STORAGE_TABLES]) {
5982
- const db = getDatabase();
5983
- const remote = await getStoragePg();
5984
- const results = [];
5985
- try {
5986
- await runStorageMigrations(remote);
5987
- for (const table of tables) {
5988
- const result = { table, direction: "pull", rows_read: 0, rows_written: 0, errors: [] };
5989
- try {
5990
- const rows = await remote.all(`SELECT * FROM ${quoteId(table)}`);
5991
- result.rows_read = rows.length;
5992
- result.rows_written = upsertSqlite(db, table, rows);
5993
- } catch (error2) {
5994
- result.errors.push(error2 instanceof Error ? error2.message : String(error2));
5995
- }
5996
- results.push(result);
5997
- }
5998
- } finally {
5999
- await remote.close();
6000
- }
6001
- return results;
6002
- }
6003
- async function syncStorageChanges(tables = [...STORAGE_TABLES]) {
6004
- return {
6005
- push: await pushStorageChanges(tables),
6006
- pull: await pullStorageChanges(tables)
6007
- };
6008
- }
6009
- function parseStorageTables(raw) {
6010
- if (!raw)
6011
- return [...STORAGE_TABLES];
6012
- const requested = raw.split(",").map((table) => table.trim()).filter(Boolean);
6013
- if (requested.length === 0)
6014
- return [...STORAGE_TABLES];
6015
- const allowed = new Set(STORAGE_TABLES);
6016
- const invalid = requested.filter((table) => !allowed.has(table));
6017
- if (invalid.length > 0)
6018
- throw new Error(`Unknown recordings sync table(s): ${invalid.join(", ")}`);
6019
- return requested;
6020
- }
6021
- var STORAGE_TABLES, TABLE_KEYS;
6022
- var init_storage_sync = __esm(() => {
6023
- init_database();
6024
- init_storage_config();
6025
- init_remote_storage();
6026
- init_pg_migrations();
6027
- STORAGE_TABLES = [
6028
- "projects",
6029
- "agents",
6030
- "recordings",
6031
- "recording_tags",
6032
- "feedback"
6033
- ];
6034
- TABLE_KEYS = {
6035
- projects: ["id"],
6036
- agents: ["id"],
6037
- recordings: ["id"],
6038
- recording_tags: ["recording_id", "tag"],
6039
- feedback: ["id"]
6040
- };
6041
- });
6042
-
6043
- // src/mcp/storage-tools.ts
6044
- function text(value) {
6045
- return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
6046
- }
6047
- function errorText(error2) {
6048
- return {
6049
- content: [{ type: "text", text: error2 instanceof Error ? error2.message : String(error2) }],
6050
- isError: true
6051
- };
6052
- }
6053
- function registerRecordingsStorageTools(server) {
6054
- server.tool("recordings_storage_status", "Show recordings local database and storage sync status", {}, async () => {
6055
- try {
6056
- return text(getStorageStatus());
6057
- } catch (error2) {
6058
- return errorText(error2);
6059
- }
6060
- });
6061
- server.tool("recordings_storage_push", "Push local recordings data to PostgreSQL", {
6062
- tables: exports_external.string().optional().describe("Comma-separated table names")
6063
- }, async ({ tables }) => {
6064
- try {
6065
- return text(await pushStorageChanges(parseStorageTables(tables)));
6066
- } catch (error2) {
6067
- return errorText(error2);
6068
- }
6069
- });
6070
- server.tool("recordings_storage_pull", "Pull PostgreSQL recordings data into the local database", {
6071
- tables: exports_external.string().optional().describe("Comma-separated table names")
6072
- }, async ({ tables }) => {
6073
- try {
6074
- return text(await pullStorageChanges(parseStorageTables(tables)));
6075
- } catch (error2) {
6076
- return errorText(error2);
6077
- }
6078
- });
6079
- server.tool("recordings_storage_sync", "Push local changes, then pull remote changes", {
6080
- tables: exports_external.string().optional().describe("Comma-separated table names")
6081
- }, async ({ tables }) => {
6082
- try {
6083
- return text(await syncStorageChanges(parseStorageTables(tables)));
6084
- } catch (error2) {
6085
- return errorText(error2);
6086
- }
6087
- });
6088
- server.tool("recordings_storage_feedback", "Save feedback for recordings", {
6089
- message: exports_external.string(),
6090
- email: exports_external.string().optional(),
6091
- category: exports_external.enum(["bug", "feature", "general"]).optional()
6092
- }, async ({ message, email, category }) => {
6093
- try {
6094
- const adapter = getAdapter();
6095
- adapter.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", message, email || null, category || "general", "recordings");
6096
- return text({ saved: true });
6097
- } catch (error2) {
6098
- return errorText(error2);
6099
- }
6100
- });
6101
- }
6102
- var init_storage_tools = __esm(() => {
6103
- init_zod();
6104
- init_database();
6105
- init_storage_sync();
6106
- });
6107
-
6108
- // package.json
6109
- var require_package = __commonJS((exports, module) => {
6110
- module.exports = {
6111
- name: "@hasna/recordings",
6112
- version: "0.1.37",
6113
- type: "module",
6114
- description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
6115
- repository: {
6116
- type: "git",
6117
- url: "git+https://github.com/hasna/recordings.git"
6118
- },
6119
- main: "dist/index.js",
6120
- types: "dist/index.d.ts",
6121
- bin: {
6122
- recordings: "dist/cli/index.js",
6123
- "recordings-mcp": "dist/mcp/index.js",
6124
- "recordings-serve": "dist/server/index.js"
6125
- },
6126
- exports: {
6127
- ".": {
6128
- import: "./dist/index.js",
6129
- types: "./dist/index.d.ts"
6130
- },
6131
- "./storage": {
6132
- import: "./dist/storage.js",
6133
- types: "./dist/storage.d.ts"
6134
- },
6135
- "./sdk": {
6136
- import: "./dist/sdk/index.js",
6137
- types: "./dist/sdk/index.d.ts"
6138
- }
6139
- },
6140
- scripts: {
6141
- clean: "rm -rf dist",
6142
- build: "bun run clean && bun run build:cli && bun run build:mcp && bun run build:serve && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
6143
- "build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
6144
- "build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
6145
- "build:serve": "bun build src/server/index.ts --target=bun --outfile=dist/server/index.js --external=@modelcontextprotocol/sdk --external=@hasna/contracts --external=openai --external=pg",
6146
- "build:lib": "bun build src/index.ts src/storage.ts src/sdk/index.ts --target=bun --outdir=dist --external=openai",
6147
- "generate:sdk": "bun run scripts/generate-sdk.ts",
6148
- migrate: "bun run scripts/migrate.ts",
6149
- typecheck: "tsc --noEmit",
6150
- test: "bun test",
6151
- "test:coverage": "bun test --coverage",
6152
- "dev:cli": "bun run src/cli/index.ts",
6153
- "dev:mcp": "bun run src/mcp/index.ts",
6154
- "verify:release": "bun run scripts/release-guard.ts",
6155
- prepack: "bun run build && bun run verify:release",
6156
- prepublishOnly: "bun run typecheck && bun run test",
6157
- postinstall: "bash scripts/install_macos_app.sh --postinstall"
6158
- },
6159
- files: [
6160
- "dist/",
6161
- "scripts/",
6162
- "src/native/Recordings/App/",
6163
- "src/native/Recordings/RecordingsLib/",
6164
- "src/native/Recordings/RecordingsTests/",
6165
- "src/native/Recordings/Package.swift",
6166
- "src/native/Recordings/Package.resolved",
6167
- "src/native/Recordings/build.sh",
6168
- "README.md",
6169
- "LICENSE"
6170
- ],
6171
- dependencies: {
6172
- "@hasna/contracts": "0.4.1",
6173
- "@hasna/events": "^0.1.11",
6174
- "@modelcontextprotocol/sdk": "^1.12.1",
6175
- chalk: "^5.4.1",
6176
- commander: "^13.1.0",
6177
- openai: "^5.1.0",
6178
- pg: "^8.13.3",
6179
- zod: "^3.24.2"
6180
- },
6181
- devDependencies: {
6182
- "@types/bun": "^1.2.5",
6183
- "@types/pg": "^8.11.11",
6184
- typescript: "^5.8.2"
6185
- },
6186
- publishConfig: {
6187
- registry: "https://registry.npmjs.org",
6188
- access: "public"
6189
- },
6190
- license: "Apache-2.0",
6191
- author: "Hasna <andrei@hasna.com>"
6192
- };
6193
- });
6194
-
6195
6313
  // src/mcp/index.ts
6196
6314
  var exports_mcp = {};
6197
6315
  __export(exports_mcp, {
@@ -6205,7 +6323,7 @@ function buildServer() {
6205
6323
  version: VERSION
6206
6324
  });
6207
6325
  const registerTool = server.tool.bind(server);
6208
- function text2(content) {
6326
+ function text(content) {
6209
6327
  return { content: [{ type: "text", text: content }] };
6210
6328
  }
6211
6329
  function errorResult(e) {
@@ -6298,7 +6416,7 @@ Params: none`
6298
6416
  };
6299
6417
  registerTool("describe_tool", "Get full param docs for any tool.", { name: exports_external.string() }, async (args) => {
6300
6418
  const doc = toolDocs[args.name];
6301
- return doc ? text2(doc) : text2(`Unknown tool: ${args.name}. Available: ${Object.keys(toolDocs).join(", ")}`);
6419
+ return doc ? text(doc) : text(`Unknown tool: ${args.name}. Available: ${Object.keys(toolDocs).join(", ")}`);
6302
6420
  });
6303
6421
  registerTool("transcribe_audio", "Transcribe audio file. Auto-enhances if needed.", {
6304
6422
  audio_path: exports_external.string(),
@@ -6317,7 +6435,7 @@ Params: none`
6317
6435
  cfg.auto_enhance = false;
6318
6436
  const transcription = await transcribeAudio(args.audio_path, cfg);
6319
6437
  const processed = await processText(transcription.text, cfg);
6320
- const recording = createRecording2({
6438
+ const recording = await getStore().createRecording({
6321
6439
  audio_path: args.audio_path,
6322
6440
  raw_text: transcription.text,
6323
6441
  processed_text: processed.mode === "enhanced" ? processed.text : undefined,
@@ -6346,7 +6464,7 @@ Params: none`
6346
6464
  });
6347
6465
  }
6348
6466
  const output = processed.mode === "enhanced" ? processed.text : transcription.text;
6349
- return text2(`${recording.id.slice(0, 8)} | ${processed.mode} | ${output}`);
6467
+ return text(`${recording.id.slice(0, 8)} | ${processed.mode} | ${output}`);
6350
6468
  } catch (e) {
6351
6469
  return errorResult(e);
6352
6470
  }
@@ -6375,7 +6493,7 @@ Params: none`
6375
6493
  enhModel = processed.enhancement_model || undefined;
6376
6494
  }
6377
6495
  }
6378
- const recording = createRecording2({
6496
+ const recording = await getStore().createRecording({
6379
6497
  raw_text: args.text,
6380
6498
  processed_text: processedText,
6381
6499
  processing_mode: mode,
@@ -6391,17 +6509,17 @@ Params: none`
6391
6509
  metadata: args.metadata
6392
6510
  });
6393
6511
  const output = processedText || args.text;
6394
- return text2(`${recording.id.slice(0, 8)} | ${mode} | ${output}`);
6512
+ return text(`${recording.id.slice(0, 8)} | ${mode} | ${output}`);
6395
6513
  } catch (e) {
6396
6514
  return errorResult(e);
6397
6515
  }
6398
6516
  });
6399
6517
  registerTool("get_recording", "Get recording by ID or prefix.", { id: exports_external.string() }, async (args) => {
6400
6518
  try {
6401
- const r = getRecording2(args.id);
6519
+ const r = await getStore().getRecording(args.id);
6402
6520
  if (!r)
6403
- return text2(`Not found: ${args.id}`);
6404
- return text2(full(r));
6521
+ return text(`Not found: ${args.id}`);
6522
+ return text(full(r));
6405
6523
  } catch (e) {
6406
6524
  return errorResult(e);
6407
6525
  }
@@ -6432,15 +6550,15 @@ Params: none`
6432
6550
  project_id: args.project_id,
6433
6551
  session_id: args.session_id
6434
6552
  };
6435
- const recordings = listRecordings2(filter);
6553
+ const recordings = await getStore().listRecordings(filter);
6436
6554
  if (recordings.length === 0)
6437
- return text2("No recordings found.");
6555
+ return text("No recordings found.");
6438
6556
  const fmt = args.full ? full : compact;
6439
6557
  const sep = args.full ? `
6440
6558
  ---
6441
6559
  ` : `
6442
6560
  `;
6443
- return text2(`${recordings.length} recording(s):${sep}${recordings.map(fmt).join(sep)}`);
6561
+ return text(`${recordings.length} recording(s):${sep}${recordings.map(fmt).join(sep)}`);
6444
6562
  } catch (e) {
6445
6563
  return errorResult(e);
6446
6564
  }
@@ -6453,39 +6571,39 @@ Params: none`
6453
6571
  full: exports_external.boolean().optional()
6454
6572
  }, async (args) => {
6455
6573
  try {
6456
- const results = searchRecordings(args.query, {
6574
+ const results = await getStore().searchRecordings(args.query, {
6457
6575
  limit: args.limit || 10,
6458
6576
  agent_id: args.agent_id,
6459
6577
  project_id: args.project_id
6460
6578
  });
6461
6579
  if (results.length === 0)
6462
- return text2("No results.");
6580
+ return text("No results.");
6463
6581
  const fmt = args.full ? full : compact;
6464
6582
  const sep = args.full ? `
6465
6583
  ---
6466
6584
  ` : `
6467
6585
  `;
6468
- return text2(`${results.length} result(s):${sep}${results.map(fmt).join(sep)}`);
6586
+ return text(`${results.length} result(s):${sep}${results.map(fmt).join(sep)}`);
6469
6587
  } catch (e) {
6470
6588
  return errorResult(e);
6471
6589
  }
6472
6590
  });
6473
6591
  registerTool("delete_recording", "Delete recording by ID.", { id: exports_external.string() }, async (args) => {
6474
6592
  try {
6475
- return text2(deleteRecording2(args.id) ? `Deleted ${args.id}` : `Not found: ${args.id}`);
6593
+ return text(await getStore().deleteRecording(args.id) ? `Deleted ${args.id}` : `Not found: ${args.id}`);
6476
6594
  } catch (e) {
6477
6595
  return errorResult(e);
6478
6596
  }
6479
6597
  });
6480
6598
  registerTool("recording_stats", "Recording stats: count, modes, duration.", {}, async () => {
6481
6599
  try {
6482
- const s = getRecordingStats2();
6600
+ const s = await getStore().getRecordingStats();
6483
6601
  let out = `Total: ${s.total} | Raw: ${s.raw} | Enhanced: ${s.enhanced} | Duration: ${(s.total_duration_ms / 1000).toFixed(1)}s`;
6484
6602
  if (Object.keys(s.by_model).length > 0) {
6485
6603
  out += `
6486
6604
  ` + Object.entries(s.by_model).map(([m, c]) => `${m}: ${c}`).join(", ");
6487
6605
  }
6488
- return text2(out);
6606
+ return text(out);
6489
6607
  } catch (e) {
6490
6608
  return errorResult(e);
6491
6609
  }
@@ -6493,25 +6611,25 @@ Params: none`
6493
6611
  registerTool("detect_enhancement", "Check if text needs AI enhancement.", { text: exports_external.string() }, async (args) => {
6494
6612
  try {
6495
6613
  const r = needsEnhancement(args.text, config);
6496
- return text2(`${r.needs ? "Yes" : "No"}: ${r.reason}`);
6614
+ return text(`${r.needs ? "Yes" : "No"}: ${r.reason}`);
6497
6615
  } catch (e) {
6498
6616
  return errorResult(e);
6499
6617
  }
6500
6618
  });
6501
6619
  registerTool("register_agent", "Register agent (idempotent).", { name: exports_external.string(), description: exports_external.string().optional(), role: exports_external.string().optional() }, async (args) => {
6502
6620
  try {
6503
- const a = registerAgent2(args.name, args.description, args.role);
6504
- return text2(`${a.id} | ${a.name} | ${a.role}`);
6621
+ const a = await getStore().registerAgent(args.name, args.description, args.role);
6622
+ return text(`${a.id} | ${a.name} | ${a.role}`);
6505
6623
  } catch (e) {
6506
6624
  return errorResult(e);
6507
6625
  }
6508
6626
  });
6509
6627
  registerTool("list_agents", "List registered agents.", {}, async () => {
6510
6628
  try {
6511
- const agents = listAgents2();
6629
+ const agents = await getStore().listAgents();
6512
6630
  if (agents.length === 0)
6513
- return text2("None.");
6514
- return text2(agents.map((a) => `${a.id} | ${a.name} | ${a.role}`).join(`
6631
+ return text("None.");
6632
+ return text(agents.map((a) => `${a.id} | ${a.name} | ${a.role}`).join(`
6515
6633
  `));
6516
6634
  } catch (e) {
6517
6635
  return errorResult(e);
@@ -6519,28 +6637,28 @@ Params: none`
6519
6637
  });
6520
6638
  registerTool("get_agent", "Get agent by ID or name.", { id: exports_external.string() }, async (args) => {
6521
6639
  try {
6522
- const a = getAgent2(args.id);
6640
+ const a = await getStore().getAgent(args.id);
6523
6641
  if (!a)
6524
- return text2(`Not found: ${args.id}`);
6525
- return text2(`${a.id} | ${a.name} | ${a.role} | ${a.last_seen_at}`);
6642
+ return text(`Not found: ${args.id}`);
6643
+ return text(`${a.id} | ${a.name} | ${a.role} | ${a.last_seen_at}`);
6526
6644
  } catch (e) {
6527
6645
  return errorResult(e);
6528
6646
  }
6529
6647
  });
6530
6648
  registerTool("register_project", "Register project (idempotent).", { name: exports_external.string(), path: exports_external.string(), description: exports_external.string().optional() }, async (args) => {
6531
6649
  try {
6532
- const p = registerProject2(args.name, args.path, args.description);
6533
- return text2(`${p.id.slice(0, 8)} | ${p.name} | ${p.path}`);
6650
+ const p = await getStore().registerProject(args.name, args.path, args.description);
6651
+ return text(`${p.id.slice(0, 8)} | ${p.name} | ${p.path}`);
6534
6652
  } catch (e) {
6535
6653
  return errorResult(e);
6536
6654
  }
6537
6655
  });
6538
6656
  registerTool("list_projects", "List registered projects.", {}, async () => {
6539
6657
  try {
6540
- const projects = listProjects2();
6658
+ const projects = await getStore().listProjects();
6541
6659
  if (projects.length === 0)
6542
- return text2("None.");
6543
- return text2(projects.map((p) => `${p.id.slice(0, 8)} | ${p.name} | ${p.path}`).join(`
6660
+ return text("None.");
6661
+ return text(projects.map((p) => `${p.id.slice(0, 8)} | ${p.name} | ${p.path}`).join(`
6544
6662
  `));
6545
6663
  } catch (e) {
6546
6664
  return errorResult(e);
@@ -6548,20 +6666,20 @@ Params: none`
6548
6666
  });
6549
6667
  registerTool("heartbeat", "Update last_seen_at to signal agent is active. Call periodically during long tasks.", { agent_id: exports_external.string().describe("Agent ID or name") }, async (args) => {
6550
6668
  try {
6551
- const agent = heartbeatAgent(args.agent_id);
6669
+ const agent = await getStore().heartbeatAgent(args.agent_id);
6552
6670
  if (!agent)
6553
- return text2(`Agent not found: ${args.agent_id}`);
6554
- return text2(`${agent.id} | ${agent.name} | last_seen: ${agent.last_seen_at}`);
6671
+ return text(`Agent not found: ${args.agent_id}`);
6672
+ return text(`${agent.id} | ${agent.name} | last_seen: ${agent.last_seen_at}`);
6555
6673
  } catch (e) {
6556
6674
  return errorResult(e);
6557
6675
  }
6558
6676
  });
6559
6677
  registerTool("set_focus", "Set active project context for this agent session.", { agent_id: exports_external.string().describe("Agent ID or name"), project_id: exports_external.string().nullable().optional().describe("Project ID to focus on, or null to clear") }, async (args) => {
6560
6678
  try {
6561
- const agent = setAgentFocus(args.agent_id, args.project_id ?? null);
6679
+ const agent = await getStore().setAgentFocus(args.agent_id, args.project_id ?? null);
6562
6680
  if (!agent)
6563
- return text2(`Agent not found: ${args.agent_id}`);
6564
- return text2(args.project_id ? `Focus set: ${args.project_id}` : "Focus cleared");
6681
+ return text(`Agent not found: ${args.agent_id}`);
6682
+ return text(args.project_id ? `Focus set: ${args.project_id}` : "Focus cleared");
6565
6683
  } catch (e) {
6566
6684
  return errorResult(e);
6567
6685
  }
@@ -6571,12 +6689,18 @@ Params: none`
6571
6689
  email: exports_external.string().optional().describe("Contact email (optional)"),
6572
6690
  category: exports_external.enum(["bug", "feature", "general"]).optional().describe("Feedback category")
6573
6691
  }, async (params) => {
6574
- const adapter = getAdapter();
6575
- const pkg = require_package();
6576
- adapter.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", params.message, params.email || null, params.category || "general", pkg.version);
6577
- return text2("Feedback saved. Thank you!");
6692
+ try {
6693
+ await getStore().saveFeedback({
6694
+ message: params.message,
6695
+ email: params.email || null,
6696
+ category: params.category || "general",
6697
+ version: VERSION
6698
+ });
6699
+ return text("Feedback saved. Thank you!");
6700
+ } catch (e) {
6701
+ return errorResult(e);
6702
+ }
6578
6703
  });
6579
- registerRecordingsStorageTools(server);
6580
6704
  return server;
6581
6705
  }
6582
6706
  var config;
@@ -6584,16 +6708,11 @@ var init_mcp = __esm(async () => {
6584
6708
  init_http();
6585
6709
  init_zod();
6586
6710
  init_config();
6587
- init_database();
6588
- init_recordings();
6589
- init_agents();
6590
- init_projects();
6711
+ init_store();
6591
6712
  init_transcriber();
6592
6713
  init_enhancer();
6593
- init_storage_tools();
6594
6714
  config = loadConfig();
6595
6715
  ensureDataDir(config);
6596
- getDatabase(config.db_path);
6597
6716
  if (false) {}
6598
6717
  });
6599
6718
 
@@ -6633,7 +6752,7 @@ function checkRateLimit(ip) {
6633
6752
  return { allowed: true };
6634
6753
  }
6635
6754
  function buildFetch() {
6636
- return async function fetch(req, server) {
6755
+ return async function fetch2(req, server) {
6637
6756
  const url = new URL(req.url);
6638
6757
  const path = url.pathname;
6639
6758
  const method = req.method.toUpperCase();
@@ -6704,8 +6823,8 @@ function buildFetch() {
6704
6823
  }
6705
6824
  async function startServer(port, options) {
6706
6825
  const hostname = options?.host || process.env.HOST || "127.0.0.1";
6707
- const fetch = buildFetch();
6708
- const server = Bun.serve({ port, hostname, fetch });
6826
+ const fetch2 = buildFetch();
6827
+ const server = Bun.serve({ port, hostname, fetch: fetch2 });
6709
6828
  const shutdown = async () => {
6710
6829
  try {
6711
6830
  const { closeCloud: closeCloud2 } = await Promise.resolve().then(() => (init_cloud(), exports_cloud));