@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.
- package/dist/cli/index.js +707 -5717
- package/dist/db/feedback.d.ts +13 -0
- package/dist/db/feedback.d.ts.map +1 -0
- package/dist/http/client.d.ts +77 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/index.d.ts +4 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +501 -5359
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +528 -5479
- package/dist/sdk/index.js +0 -31
- package/dist/server/index.js +582 -463
- package/dist/server/repo.d.ts +10 -0
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.d.ts +4 -6
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +706 -5286
- package/dist/store.d.ts +40 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/cli/storage.d.ts +0 -3
- package/dist/cli/storage.d.ts.map +0 -1
- package/dist/db/pg-migrate.d.ts +0 -8
- package/dist/db/pg-migrate.d.ts.map +0 -1
- package/dist/db/storage-config.d.ts +0 -28
- package/dist/db/storage-config.d.ts.map +0 -1
- package/dist/db/storage-sync.d.ts +0 -36
- package/dist/db/storage-sync.d.ts.map +0 -1
- package/dist/mcp/storage-tools.d.ts +0 -3
- package/dist/mcp/storage-tools.d.ts.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
6519
|
+
const r = await getStore().getRecording(args.id);
|
|
6402
6520
|
if (!r)
|
|
6403
|
-
return
|
|
6404
|
-
return
|
|
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 =
|
|
6553
|
+
const recordings = await getStore().listRecordings(filter);
|
|
6436
6554
|
if (recordings.length === 0)
|
|
6437
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
6504
|
-
return
|
|
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 =
|
|
6629
|
+
const agents = await getStore().listAgents();
|
|
6512
6630
|
if (agents.length === 0)
|
|
6513
|
-
return
|
|
6514
|
-
return
|
|
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 =
|
|
6640
|
+
const a = await getStore().getAgent(args.id);
|
|
6523
6641
|
if (!a)
|
|
6524
|
-
return
|
|
6525
|
-
return
|
|
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 =
|
|
6533
|
-
return
|
|
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 =
|
|
6658
|
+
const projects = await getStore().listProjects();
|
|
6541
6659
|
if (projects.length === 0)
|
|
6542
|
-
return
|
|
6543
|
-
return
|
|
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
|
|
6554
|
-
return
|
|
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
|
|
6564
|
-
return
|
|
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
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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));
|