@hasna/recordings 0.1.23 → 0.1.25
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/LICENSE +191 -170
- package/README.md +16 -7
- package/dist/cli/index.js +119 -65
- package/dist/cli/storage.d.ts +3 -0
- package/dist/cli/storage.d.ts.map +1 -0
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/db/storage-config.d.ts +27 -0
- package/dist/db/storage-config.d.ts.map +1 -0
- package/dist/db/storage-sync.d.ts +36 -0
- package/dist/db/storage-sync.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -50
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/transcriber.d.ts +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +13 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +519 -408
- package/dist/mcp/storage-tools.d.ts +3 -0
- package/dist/mcp/storage-tools.d.ts.map +1 -0
- package/dist/storage.d.ts +7 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +5656 -0
- package/dist/version.d.ts +1 -1
- package/package.json +8 -3
- package/scripts/install_macos_app.sh +17 -0
- package/src/native/Recordings/App/RecordingsApp.swift +3 -2
- package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +2 -0
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +101 -80
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +26 -0
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +13 -1
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +103 -31
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +5 -1
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +12 -0
- package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +49 -0
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +54 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +58 -0
- package/dist/cli/cloud.d.ts +0 -3
- package/dist/cli/cloud.d.ts.map +0 -1
- package/dist/db/cloud-config.d.ts +0 -14
- package/dist/db/cloud-config.d.ts.map +0 -1
- package/dist/db/cloud-sync.d.ts +0 -29
- package/dist/db/cloud-sync.d.ts.map +0 -1
- package/dist/mcp/cloud-tools.d.ts +0 -3
- package/dist/mcp/cloud-tools.d.ts.map +0 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4920,7 +4920,7 @@ var require_lib2 = __commonJS((exports, module) => {
|
|
|
4920
4920
|
var require_package = __commonJS((exports, module) => {
|
|
4921
4921
|
module.exports = {
|
|
4922
4922
|
name: "@hasna/recordings",
|
|
4923
|
-
version: "0.1.
|
|
4923
|
+
version: "0.1.25",
|
|
4924
4924
|
type: "module",
|
|
4925
4925
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
4926
4926
|
repository: {
|
|
@@ -4937,13 +4937,18 @@ var require_package = __commonJS((exports, module) => {
|
|
|
4937
4937
|
".": {
|
|
4938
4938
|
import: "./dist/index.js",
|
|
4939
4939
|
types: "./dist/index.d.ts"
|
|
4940
|
+
},
|
|
4941
|
+
"./storage": {
|
|
4942
|
+
import: "./dist/storage.js",
|
|
4943
|
+
types: "./dist/storage.d.ts"
|
|
4940
4944
|
}
|
|
4941
4945
|
},
|
|
4942
4946
|
scripts: {
|
|
4943
|
-
|
|
4947
|
+
clean: "rm -rf dist",
|
|
4948
|
+
build: "bun run clean && bun run build:cli && bun run build:mcp && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
|
|
4944
4949
|
"build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
|
|
4945
4950
|
"build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
|
|
4946
|
-
"build:lib": "bun build src/index.ts --target=bun --
|
|
4951
|
+
"build:lib": "bun build src/index.ts src/storage.ts --target=bun --outdir=dist --external=openai",
|
|
4947
4952
|
typecheck: "tsc --noEmit",
|
|
4948
4953
|
test: "bun test",
|
|
4949
4954
|
"test:coverage": "bun test --coverage",
|
|
@@ -4990,6 +4995,51 @@ var require_package = __commonJS((exports, module) => {
|
|
|
4990
4995
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4991
4996
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4992
4997
|
|
|
4998
|
+
// src/mcp/http.ts
|
|
4999
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5000
|
+
var DEFAULT_MCP_HTTP_PORT = 8873;
|
|
5001
|
+
var MCP_HTTP_HOST = "127.0.0.1";
|
|
5002
|
+
function isStdioMode(args) {
|
|
5003
|
+
return args.includes("--stdio") || process.env.MCP_STDIO === "1";
|
|
5004
|
+
}
|
|
5005
|
+
function resolveMcpHttpPort(args) {
|
|
5006
|
+
const portIdx = args.indexOf("--port");
|
|
5007
|
+
if (portIdx >= 0 && args[portIdx + 1]) {
|
|
5008
|
+
return Number(args[portIdx + 1]);
|
|
5009
|
+
}
|
|
5010
|
+
const envPort = process.env.MCP_HTTP_PORT;
|
|
5011
|
+
if (envPort)
|
|
5012
|
+
return Number(envPort);
|
|
5013
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
5014
|
+
}
|
|
5015
|
+
async function handleMcpRequest(req, buildServer) {
|
|
5016
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
5017
|
+
sessionIdGenerator: undefined
|
|
5018
|
+
});
|
|
5019
|
+
const server = buildServer();
|
|
5020
|
+
await server.connect(transport);
|
|
5021
|
+
return transport.handleRequest(req);
|
|
5022
|
+
}
|
|
5023
|
+
function startMcpHttpServer(options) {
|
|
5024
|
+
const { name, port, buildServer } = options;
|
|
5025
|
+
const server = Bun.serve({
|
|
5026
|
+
hostname: MCP_HTTP_HOST,
|
|
5027
|
+
port,
|
|
5028
|
+
async fetch(req) {
|
|
5029
|
+
const url = new URL(req.url);
|
|
5030
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
5031
|
+
return Response.json({ status: "ok", name });
|
|
5032
|
+
}
|
|
5033
|
+
if (url.pathname === "/mcp") {
|
|
5034
|
+
return handleMcpRequest(req, buildServer);
|
|
5035
|
+
}
|
|
5036
|
+
return new Response("Not Found", { status: 404 });
|
|
5037
|
+
}
|
|
5038
|
+
});
|
|
5039
|
+
console.error(`${name}-mcp HTTP listening on http://${MCP_HTTP_HOST}:${port}/mcp`);
|
|
5040
|
+
return server;
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4993
5043
|
// node_modules/zod/v3/external.js
|
|
4994
5044
|
var exports_external = {};
|
|
4995
5045
|
__export(exports_external, {
|
|
@@ -9598,9 +9648,18 @@ async function transcribeAudio(audioPath, config, options = {}) {
|
|
|
9598
9648
|
};
|
|
9599
9649
|
} catch (error) {
|
|
9600
9650
|
const msg = error instanceof Error ? error.message : String(error);
|
|
9601
|
-
throw new TranscriptionError(`Transcription failed: ${msg}`);
|
|
9651
|
+
throw new TranscriptionError(`Transcription failed: ${describeTranscriptionFailure(msg)}`);
|
|
9602
9652
|
}
|
|
9603
9653
|
}
|
|
9654
|
+
function describeTranscriptionFailure(message) {
|
|
9655
|
+
if (/401|incorrect api key|invalid_api_key/i.test(message)) {
|
|
9656
|
+
return "OpenAI API key invalid or expired (401). Update it in ~/.hasna/recordings/config.json, the OPENAI_API_KEY env var, or the Recordings app Settings.";
|
|
9657
|
+
}
|
|
9658
|
+
if (/429|exceeded your current quota|insufficient_quota/i.test(message)) {
|
|
9659
|
+
return "OpenAI quota exceeded (429). Check the OpenAI account plan and billing.";
|
|
9660
|
+
}
|
|
9661
|
+
return message;
|
|
9662
|
+
}
|
|
9604
9663
|
function buildVerbatimPrompt(context) {
|
|
9605
9664
|
const base = "Transcribe the speaker's words verbatim. Output only words that were spoken. Do not summarize, paraphrase, rewrite, clean up grammar, add explanations, or infer missing words. Preserve names, acronyms, technical terms, punctuation, and casing when audible.";
|
|
9606
9665
|
const trimmed = context?.trim();
|
|
@@ -9718,7 +9777,7 @@ ${systemPrompt}` : basePrompt;
|
|
|
9718
9777
|
};
|
|
9719
9778
|
} catch (error) {
|
|
9720
9779
|
const msg = error instanceof Error ? error.message : String(error);
|
|
9721
|
-
throw new EnhancementError(`Enhancement failed: ${msg}`);
|
|
9780
|
+
throw new EnhancementError(`Enhancement failed: ${describeTranscriptionFailure(msg)}`);
|
|
9722
9781
|
}
|
|
9723
9782
|
}
|
|
9724
9783
|
async function processText(rawText, config, systemPrompt) {
|
|
@@ -9738,57 +9797,82 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
9738
9797
|
}
|
|
9739
9798
|
|
|
9740
9799
|
// src/version.ts
|
|
9741
|
-
var VERSION = "0.1.
|
|
9800
|
+
var VERSION = "0.1.25";
|
|
9742
9801
|
|
|
9743
|
-
// src/db/
|
|
9802
|
+
// src/db/storage-config.ts
|
|
9744
9803
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9745
9804
|
import { homedir as homedir2 } from "os";
|
|
9746
9805
|
import { join as join2 } from "path";
|
|
9747
|
-
var
|
|
9748
|
-
|
|
9749
|
-
|
|
9806
|
+
var STORAGE_CONFIG_PATH = join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
|
|
9807
|
+
var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
|
|
9808
|
+
var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
|
|
9809
|
+
var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
|
|
9810
|
+
var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
|
|
9811
|
+
var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
|
|
9812
|
+
function readEnv(name) {
|
|
9813
|
+
const value = process.env[name]?.trim();
|
|
9814
|
+
return value || undefined;
|
|
9815
|
+
}
|
|
9816
|
+
function normalizeMode(value) {
|
|
9817
|
+
const normalized = value?.trim().toLowerCase();
|
|
9818
|
+
if (normalized === "local" || normalized === "hybrid" || normalized === "remote")
|
|
9819
|
+
return normalized;
|
|
9820
|
+
return;
|
|
9821
|
+
}
|
|
9822
|
+
function getStorageDatabaseEnvName() {
|
|
9823
|
+
for (const name of STORAGE_DATABASE_ENV) {
|
|
9824
|
+
if (readEnv(name))
|
|
9825
|
+
return name;
|
|
9826
|
+
}
|
|
9827
|
+
return null;
|
|
9750
9828
|
}
|
|
9751
|
-
function
|
|
9752
|
-
|
|
9829
|
+
function getStorageDatabaseEnv() {
|
|
9830
|
+
const name = getStorageDatabaseEnvName();
|
|
9831
|
+
return name ? { name } : null;
|
|
9753
9832
|
}
|
|
9754
|
-
function
|
|
9833
|
+
function getStorageDatabaseUrl() {
|
|
9834
|
+
const env = getStorageDatabaseEnv();
|
|
9835
|
+
return env ? readEnv(env.name) : undefined;
|
|
9836
|
+
}
|
|
9837
|
+
function getStorageConfig() {
|
|
9755
9838
|
const config = {
|
|
9756
9839
|
mode: "local",
|
|
9757
9840
|
rds: {
|
|
9758
9841
|
host: "",
|
|
9759
9842
|
port: 5432,
|
|
9760
9843
|
username: "",
|
|
9761
|
-
password_env: "
|
|
9844
|
+
password_env: "RECORDINGS_DATABASE_PASSWORD",
|
|
9762
9845
|
ssl: true
|
|
9763
9846
|
}
|
|
9764
9847
|
};
|
|
9765
|
-
if (existsSync2(
|
|
9848
|
+
if (existsSync2(STORAGE_CONFIG_PATH)) {
|
|
9766
9849
|
try {
|
|
9767
|
-
const raw = JSON.parse(readFileSync2(
|
|
9768
|
-
config.mode = raw.mode ?? config.mode;
|
|
9850
|
+
const raw = JSON.parse(readFileSync2(STORAGE_CONFIG_PATH, "utf-8"));
|
|
9851
|
+
config.mode = normalizeMode(raw.mode) ?? config.mode;
|
|
9769
9852
|
config.rds = { ...config.rds, ...raw.rds ?? {} };
|
|
9770
9853
|
} catch {}
|
|
9771
9854
|
}
|
|
9772
|
-
const modeOverride =
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9855
|
+
const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
|
|
9856
|
+
const normalizedMode = normalizeMode(modeOverride);
|
|
9857
|
+
if (normalizedMode) {
|
|
9858
|
+
config.mode = normalizedMode;
|
|
9859
|
+
} else if (getStorageDatabaseUrl() && config.mode === "local") {
|
|
9776
9860
|
config.mode = "hybrid";
|
|
9777
9861
|
}
|
|
9778
9862
|
return config;
|
|
9779
9863
|
}
|
|
9780
|
-
function
|
|
9781
|
-
const direct =
|
|
9864
|
+
function getStorageConnectionString(dbName = "recordings") {
|
|
9865
|
+
const direct = getStorageDatabaseUrl();
|
|
9782
9866
|
if (direct)
|
|
9783
9867
|
return direct;
|
|
9784
|
-
const config =
|
|
9868
|
+
const config = getStorageConfig();
|
|
9785
9869
|
const { host, port, username, password_env, ssl } = config.rds;
|
|
9786
9870
|
if (!host || !username) {
|
|
9787
|
-
throw new Error("
|
|
9871
|
+
throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
|
|
9788
9872
|
}
|
|
9789
9873
|
const password = process.env[password_env];
|
|
9790
9874
|
if (!password) {
|
|
9791
|
-
throw new Error(`
|
|
9875
|
+
throw new Error(`Storage database password is not set. Export ${password_env}.`);
|
|
9792
9876
|
}
|
|
9793
9877
|
const sslParam = ssl ? "?sslmode=require" : "";
|
|
9794
9878
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
@@ -9915,8 +9999,8 @@ var PG_MIGRATIONS = [
|
|
|
9915
9999
|
`ALTER TABLE agents ADD COLUMN IF NOT EXISTS active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL`
|
|
9916
10000
|
];
|
|
9917
10001
|
|
|
9918
|
-
// src/db/
|
|
9919
|
-
var
|
|
10002
|
+
// src/db/storage-sync.ts
|
|
10003
|
+
var STORAGE_TABLES = [
|
|
9920
10004
|
"projects",
|
|
9921
10005
|
"agents",
|
|
9922
10006
|
"recordings",
|
|
@@ -9975,21 +10059,26 @@ function upsertSqlite(db, table, rows) {
|
|
|
9975
10059
|
}
|
|
9976
10060
|
return written;
|
|
9977
10061
|
}
|
|
9978
|
-
async function
|
|
9979
|
-
return new PgAdapterAsync(
|
|
10062
|
+
async function getStoragePg() {
|
|
10063
|
+
return new PgAdapterAsync(getStorageConnectionString("recordings"));
|
|
9980
10064
|
}
|
|
9981
|
-
async function
|
|
10065
|
+
async function runStorageMigrations(remote) {
|
|
9982
10066
|
for (const migration of PG_MIGRATIONS) {
|
|
9983
10067
|
await remote.exec(migration);
|
|
9984
10068
|
}
|
|
9985
10069
|
}
|
|
9986
|
-
function
|
|
9987
|
-
const config =
|
|
10070
|
+
function getStorageStatus(db = getDatabase()) {
|
|
10071
|
+
const config = getStorageConfig();
|
|
10072
|
+
const activeEnv = getStorageDatabaseEnv();
|
|
9988
10073
|
return {
|
|
10074
|
+
configured: Boolean(activeEnv),
|
|
9989
10075
|
mode: config.mode,
|
|
9990
|
-
enabled: config.mode === "hybrid" || config.mode === "
|
|
10076
|
+
enabled: config.mode === "hybrid" || config.mode === "remote",
|
|
10077
|
+
env: STORAGE_DATABASE_ENV,
|
|
10078
|
+
activeEnv: activeEnv?.name ?? null,
|
|
10079
|
+
service: "recordings",
|
|
9991
10080
|
db_path: getDbPath(),
|
|
9992
|
-
tables:
|
|
10081
|
+
tables: STORAGE_TABLES.map((table) => {
|
|
9993
10082
|
try {
|
|
9994
10083
|
const row = db.query(`SELECT COUNT(*) as count FROM ${quoteId(table)}`).get();
|
|
9995
10084
|
return { table, rows: row.count };
|
|
@@ -9999,12 +10088,12 @@ function getCloudStatus(db = getDatabase()) {
|
|
|
9999
10088
|
})
|
|
10000
10089
|
};
|
|
10001
10090
|
}
|
|
10002
|
-
async function
|
|
10091
|
+
async function pushStorageChanges(tables = [...STORAGE_TABLES]) {
|
|
10003
10092
|
const db = getDatabase();
|
|
10004
|
-
const remote = await
|
|
10093
|
+
const remote = await getStoragePg();
|
|
10005
10094
|
const results = [];
|
|
10006
10095
|
try {
|
|
10007
|
-
await
|
|
10096
|
+
await runStorageMigrations(remote);
|
|
10008
10097
|
for (const table of tables) {
|
|
10009
10098
|
const result = { table, direction: "push", rows_read: 0, rows_written: 0, errors: [] };
|
|
10010
10099
|
try {
|
|
@@ -10021,12 +10110,12 @@ async function pushCloudChanges(tables = [...CLOUD_TABLES]) {
|
|
|
10021
10110
|
}
|
|
10022
10111
|
return results;
|
|
10023
10112
|
}
|
|
10024
|
-
async function
|
|
10113
|
+
async function pullStorageChanges(tables = [...STORAGE_TABLES]) {
|
|
10025
10114
|
const db = getDatabase();
|
|
10026
|
-
const remote = await
|
|
10115
|
+
const remote = await getStoragePg();
|
|
10027
10116
|
const results = [];
|
|
10028
10117
|
try {
|
|
10029
|
-
await
|
|
10118
|
+
await runStorageMigrations(remote);
|
|
10030
10119
|
for (const table of tables) {
|
|
10031
10120
|
const result = { table, direction: "pull", rows_read: 0, rows_written: 0, errors: [] };
|
|
10032
10121
|
try {
|
|
@@ -10043,20 +10132,26 @@ async function pullCloudChanges(tables = [...CLOUD_TABLES]) {
|
|
|
10043
10132
|
}
|
|
10044
10133
|
return results;
|
|
10045
10134
|
}
|
|
10046
|
-
async function
|
|
10135
|
+
async function syncStorageChanges(tables = [...STORAGE_TABLES]) {
|
|
10047
10136
|
return {
|
|
10048
|
-
push: await
|
|
10049
|
-
pull: await
|
|
10137
|
+
push: await pushStorageChanges(tables),
|
|
10138
|
+
pull: await pullStorageChanges(tables)
|
|
10050
10139
|
};
|
|
10051
10140
|
}
|
|
10052
|
-
function
|
|
10141
|
+
function parseStorageTables(raw) {
|
|
10053
10142
|
if (!raw)
|
|
10054
|
-
return [...
|
|
10143
|
+
return [...STORAGE_TABLES];
|
|
10055
10144
|
const requested = raw.split(",").map((table) => table.trim()).filter(Boolean);
|
|
10056
|
-
|
|
10145
|
+
if (requested.length === 0)
|
|
10146
|
+
return [...STORAGE_TABLES];
|
|
10147
|
+
const allowed = new Set(STORAGE_TABLES);
|
|
10148
|
+
const invalid = requested.filter((table) => !allowed.has(table));
|
|
10149
|
+
if (invalid.length > 0)
|
|
10150
|
+
throw new Error(`Unknown recordings sync table(s): ${invalid.join(", ")}`);
|
|
10151
|
+
return requested;
|
|
10057
10152
|
}
|
|
10058
10153
|
|
|
10059
|
-
// src/mcp/
|
|
10154
|
+
// src/mcp/storage-tools.ts
|
|
10060
10155
|
function text(value) {
|
|
10061
10156
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
10062
10157
|
}
|
|
@@ -10066,42 +10161,42 @@ function errorText(error) {
|
|
|
10066
10161
|
isError: true
|
|
10067
10162
|
};
|
|
10068
10163
|
}
|
|
10069
|
-
function
|
|
10070
|
-
server.tool("
|
|
10164
|
+
function registerRecordingsStorageTools(server) {
|
|
10165
|
+
server.tool("recordings_storage_status", "Show recordings local database and storage sync status", {}, async () => {
|
|
10071
10166
|
try {
|
|
10072
|
-
return text(
|
|
10167
|
+
return text(getStorageStatus());
|
|
10073
10168
|
} catch (error) {
|
|
10074
10169
|
return errorText(error);
|
|
10075
10170
|
}
|
|
10076
10171
|
});
|
|
10077
|
-
server.tool("
|
|
10172
|
+
server.tool("recordings_storage_push", "Push local recordings data to PostgreSQL", {
|
|
10078
10173
|
tables: exports_external.string().optional().describe("Comma-separated table names")
|
|
10079
10174
|
}, async ({ tables }) => {
|
|
10080
10175
|
try {
|
|
10081
|
-
return text(await
|
|
10176
|
+
return text(await pushStorageChanges(parseStorageTables(tables)));
|
|
10082
10177
|
} catch (error) {
|
|
10083
10178
|
return errorText(error);
|
|
10084
10179
|
}
|
|
10085
10180
|
});
|
|
10086
|
-
server.tool("
|
|
10181
|
+
server.tool("recordings_storage_pull", "Pull PostgreSQL recordings data into the local database", {
|
|
10087
10182
|
tables: exports_external.string().optional().describe("Comma-separated table names")
|
|
10088
10183
|
}, async ({ tables }) => {
|
|
10089
10184
|
try {
|
|
10090
|
-
return text(await
|
|
10185
|
+
return text(await pullStorageChanges(parseStorageTables(tables)));
|
|
10091
10186
|
} catch (error) {
|
|
10092
10187
|
return errorText(error);
|
|
10093
10188
|
}
|
|
10094
10189
|
});
|
|
10095
|
-
server.tool("
|
|
10190
|
+
server.tool("recordings_storage_sync", "Push local changes, then pull remote changes", {
|
|
10096
10191
|
tables: exports_external.string().optional().describe("Comma-separated table names")
|
|
10097
10192
|
}, async ({ tables }) => {
|
|
10098
10193
|
try {
|
|
10099
|
-
return text(await
|
|
10194
|
+
return text(await syncStorageChanges(parseStorageTables(tables)));
|
|
10100
10195
|
} catch (error) {
|
|
10101
10196
|
return errorText(error);
|
|
10102
10197
|
}
|
|
10103
10198
|
});
|
|
10104
|
-
server.tool("
|
|
10199
|
+
server.tool("recordings_storage_feedback", "Save feedback for recordings", {
|
|
10105
10200
|
message: exports_external.string(),
|
|
10106
10201
|
email: exports_external.string().optional(),
|
|
10107
10202
|
category: exports_external.enum(["bug", "feature", "general"]).optional()
|
|
@@ -10120,382 +10215,398 @@ function registerRecordingsCloudTools(server) {
|
|
|
10120
10215
|
var config = loadConfig();
|
|
10121
10216
|
ensureDataDir(config);
|
|
10122
10217
|
getDatabase(config.db_path);
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
}
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
}
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
}
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10218
|
+
function buildServer() {
|
|
10219
|
+
const server = new McpServer({
|
|
10220
|
+
name: "recordings",
|
|
10221
|
+
version: VERSION
|
|
10222
|
+
});
|
|
10223
|
+
const registerTool = server.tool.bind(server);
|
|
10224
|
+
function text2(content) {
|
|
10225
|
+
return { content: [{ type: "text", text: content }] };
|
|
10226
|
+
}
|
|
10227
|
+
function errorResult(e) {
|
|
10228
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10229
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
10230
|
+
}
|
|
10231
|
+
function compact(r) {
|
|
10232
|
+
const t = (r.processed_text || r.raw_text).slice(0, 80);
|
|
10233
|
+
return `${r.id.slice(0, 8)} | ${r.processing_mode} | ${r.created_at.slice(0, 16)} | ${t}${t.length >= 80 ? "..." : ""}`;
|
|
10234
|
+
}
|
|
10235
|
+
function full(r) {
|
|
10236
|
+
const lines = [`ID: ${r.id}`, `Mode: ${r.processing_mode}`, `Model: ${r.model_used}`];
|
|
10237
|
+
if (r.enhancement_model)
|
|
10238
|
+
lines.push(`Enhanced by: ${r.enhancement_model}`);
|
|
10239
|
+
if (r.duration_ms)
|
|
10240
|
+
lines.push(`Duration: ${(r.duration_ms / 1000).toFixed(1)}s`);
|
|
10241
|
+
if (r.language)
|
|
10242
|
+
lines.push(`Language: ${r.language}`);
|
|
10243
|
+
if (r.tags.length > 0)
|
|
10244
|
+
lines.push(`Tags: ${r.tags.join(", ")}`);
|
|
10245
|
+
if (r.agent_id)
|
|
10246
|
+
lines.push(`Agent: ${r.agent_id}`);
|
|
10247
|
+
if (r.project_id)
|
|
10248
|
+
lines.push(`Project: ${r.project_id}`);
|
|
10249
|
+
if (r.session_id)
|
|
10250
|
+
lines.push(`Session: ${r.session_id}`);
|
|
10251
|
+
lines.push(`Created: ${r.created_at}`);
|
|
10252
|
+
lines.push(`Text: ${r.raw_text}`);
|
|
10253
|
+
if (r.processed_text && r.processed_text !== r.raw_text) {
|
|
10254
|
+
lines.push(`Enhanced: ${r.processed_text}`);
|
|
10255
|
+
}
|
|
10256
|
+
return lines.join(`
|
|
10161
10257
|
`);
|
|
10162
|
-
}
|
|
10163
|
-
async function saveRecordingMemento(args) {
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
}
|
|
10187
|
-
|
|
10188
|
-
|
|
10258
|
+
}
|
|
10259
|
+
async function saveRecordingMemento(args) {
|
|
10260
|
+
try {
|
|
10261
|
+
const proc = Bun.spawn([
|
|
10262
|
+
"mementos",
|
|
10263
|
+
"save",
|
|
10264
|
+
"--scope",
|
|
10265
|
+
"shared",
|
|
10266
|
+
"--category",
|
|
10267
|
+
"history",
|
|
10268
|
+
"--importance",
|
|
10269
|
+
"5",
|
|
10270
|
+
"--tags",
|
|
10271
|
+
"recording,transcription",
|
|
10272
|
+
"--summary",
|
|
10273
|
+
args.summary,
|
|
10274
|
+
args.key,
|
|
10275
|
+
args.value
|
|
10276
|
+
], {
|
|
10277
|
+
stdout: "ignore",
|
|
10278
|
+
stderr: "ignore"
|
|
10279
|
+
});
|
|
10280
|
+
await proc.exited;
|
|
10281
|
+
} catch {}
|
|
10282
|
+
}
|
|
10283
|
+
const toolDocs = {
|
|
10284
|
+
transcribe_audio: `Transcribe audio file. Auto-enhances if needed.
|
|
10189
10285
|
Params: audio_path (string, required): path to wav/mp3/m4a/webm | language (string): ISO code e.g. en/es/fr | no_enhance (bool): skip AI enhancement | tags (string[]): tags | agent_id (string) | project_id (string) | session_id (string)`,
|
|
10190
|
-
|
|
10286
|
+
save_recording: `Save text as recording. Auto-enhances if needed.
|
|
10191
10287
|
Params: text (string, required): text to save | enhance (bool): force enhancement | tags (string[]) | agent_id (string) | project_id (string) | session_id (string) | metadata (object)`,
|
|
10192
|
-
|
|
10288
|
+
get_recording: `Get recording by ID or prefix.
|
|
10193
10289
|
Params: id (string, required): recording ID or prefix`,
|
|
10194
|
-
|
|
10290
|
+
list_recordings: `List recordings, compact by default, most recent first.
|
|
10195
10291
|
Params: limit (number, default 10) | offset (number) | processing_mode ('raw'|'enhanced') | tags (string[]) | search (string): text search | since/until (ISO date) | agent_id | project_id | session_id | full (bool): verbose output`,
|
|
10196
|
-
|
|
10292
|
+
search_recordings: `Search recordings by text content.
|
|
10197
10293
|
Params: query (string, required) | limit (number, default 10) | agent_id | project_id | full (bool): verbose output`,
|
|
10198
|
-
|
|
10294
|
+
delete_recording: `Delete recording by ID.
|
|
10199
10295
|
Params: id (string, required)`,
|
|
10200
|
-
|
|
10296
|
+
recording_stats: `Recording count, mode breakdown, duration.
|
|
10201
10297
|
Params: none`,
|
|
10202
|
-
|
|
10298
|
+
detect_enhancement: `Check if text needs AI enhancement.
|
|
10203
10299
|
Params: text (string, required)`,
|
|
10204
|
-
|
|
10300
|
+
register_agent: `Register agent (idempotent). Auto-updates last_seen_at on re-register.
|
|
10205
10301
|
Params: name (string, required) | description (string) | role (string)`,
|
|
10206
|
-
|
|
10302
|
+
list_agents: `List registered agents.
|
|
10207
10303
|
Params: none`,
|
|
10208
|
-
|
|
10304
|
+
get_agent: `Get agent by ID or name.
|
|
10209
10305
|
Params: id (string, required)`,
|
|
10210
|
-
|
|
10306
|
+
heartbeat: `Update last_seen_at to signal agent is active.
|
|
10211
10307
|
Params: agent_id (string, required): agent ID or name`,
|
|
10212
|
-
|
|
10308
|
+
set_focus: `Set active project context for this agent session.
|
|
10213
10309
|
Params: agent_id (string, required) | project_id (string, nullable): project ID or null to clear`,
|
|
10214
|
-
|
|
10310
|
+
register_project: `Register project (idempotent).
|
|
10215
10311
|
Params: name (string, required) | path (string, required): absolute path | description (string)`,
|
|
10216
|
-
|
|
10312
|
+
list_projects: `List registered projects.
|
|
10217
10313
|
Params: none`
|
|
10218
|
-
};
|
|
10219
|
-
registerTool("describe_tool", "Get full param docs for any tool.", { name: exports_external.string() }, async (args) => {
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
});
|
|
10223
|
-
registerTool("transcribe_audio", "Transcribe audio file. Auto-enhances if needed.", {
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
}, async (args) => {
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
});
|
|
10254
|
-
if (args.agent_id) {
|
|
10255
|
-
await saveRecordingMemento({
|
|
10256
|
-
key: `recording-${recording.id}`,
|
|
10257
|
-
value: JSON.stringify({
|
|
10258
|
-
recording_id: recording.id,
|
|
10259
|
-
text: processed.mode === "enhanced" ? processed.text : transcription.text,
|
|
10260
|
-
agent_id: args.agent_id,
|
|
10261
|
-
project_id: args.project_id,
|
|
10262
|
-
session_id: args.session_id,
|
|
10263
|
-
created_at: recording.created_at
|
|
10264
|
-
}),
|
|
10265
|
-
summary: `Recording ${recording.id.slice(0, 8)} for ${args.agent_id}`
|
|
10314
|
+
};
|
|
10315
|
+
registerTool("describe_tool", "Get full param docs for any tool.", { name: exports_external.string() }, async (args) => {
|
|
10316
|
+
const doc = toolDocs[args.name];
|
|
10317
|
+
return doc ? text2(doc) : text2(`Unknown tool: ${args.name}. Available: ${Object.keys(toolDocs).join(", ")}`);
|
|
10318
|
+
});
|
|
10319
|
+
registerTool("transcribe_audio", "Transcribe audio file. Auto-enhances if needed.", {
|
|
10320
|
+
audio_path: exports_external.string(),
|
|
10321
|
+
language: exports_external.string().optional(),
|
|
10322
|
+
no_enhance: exports_external.boolean().optional(),
|
|
10323
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10324
|
+
agent_id: exports_external.string().optional(),
|
|
10325
|
+
project_id: exports_external.string().optional(),
|
|
10326
|
+
session_id: exports_external.string().optional()
|
|
10327
|
+
}, async (args) => {
|
|
10328
|
+
try {
|
|
10329
|
+
const cfg = { ...config };
|
|
10330
|
+
if (args.language)
|
|
10331
|
+
cfg.language = args.language;
|
|
10332
|
+
if (args.no_enhance)
|
|
10333
|
+
cfg.auto_enhance = false;
|
|
10334
|
+
const transcription = await transcribeAudio(args.audio_path, cfg);
|
|
10335
|
+
const processed = await processText(transcription.text, cfg);
|
|
10336
|
+
const recording = createRecording({
|
|
10337
|
+
audio_path: args.audio_path,
|
|
10338
|
+
raw_text: transcription.text,
|
|
10339
|
+
processed_text: processed.mode === "enhanced" ? processed.text : undefined,
|
|
10340
|
+
processing_mode: processed.mode,
|
|
10341
|
+
model_used: transcription.model,
|
|
10342
|
+
enhancement_model: processed.enhancement_model || undefined,
|
|
10343
|
+
duration_ms: transcription.duration_ms,
|
|
10344
|
+
language: transcription.language || undefined,
|
|
10345
|
+
tags: args.tags,
|
|
10346
|
+
agent_id: args.agent_id,
|
|
10347
|
+
project_id: args.project_id,
|
|
10348
|
+
session_id: args.session_id
|
|
10266
10349
|
});
|
|
10350
|
+
if (args.agent_id) {
|
|
10351
|
+
await saveRecordingMemento({
|
|
10352
|
+
key: `recording-${recording.id}`,
|
|
10353
|
+
value: JSON.stringify({
|
|
10354
|
+
recording_id: recording.id,
|
|
10355
|
+
text: processed.mode === "enhanced" ? processed.text : transcription.text,
|
|
10356
|
+
agent_id: args.agent_id,
|
|
10357
|
+
project_id: args.project_id,
|
|
10358
|
+
session_id: args.session_id,
|
|
10359
|
+
created_at: recording.created_at
|
|
10360
|
+
}),
|
|
10361
|
+
summary: `Recording ${recording.id.slice(0, 8)} for ${args.agent_id}`
|
|
10362
|
+
});
|
|
10363
|
+
}
|
|
10364
|
+
const output = processed.mode === "enhanced" ? processed.text : transcription.text;
|
|
10365
|
+
return text2(`${recording.id.slice(0, 8)} | ${processed.mode} | ${output}`);
|
|
10366
|
+
} catch (e) {
|
|
10367
|
+
return errorResult(e);
|
|
10267
10368
|
}
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
}
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
});
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
const recordings = listRecordings(filter);
|
|
10356
|
-
if (recordings.length === 0)
|
|
10357
|
-
return text2("No recordings found.");
|
|
10358
|
-
const fmt = args.full ? full : compact;
|
|
10359
|
-
const sep = args.full ? `
|
|
10369
|
+
});
|
|
10370
|
+
registerTool("save_recording", "Save text as recording. Auto-enhances if needed.", {
|
|
10371
|
+
text: exports_external.string(),
|
|
10372
|
+
enhance: exports_external.boolean().optional(),
|
|
10373
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10374
|
+
agent_id: exports_external.string().optional(),
|
|
10375
|
+
project_id: exports_external.string().optional(),
|
|
10376
|
+
session_id: exports_external.string().optional(),
|
|
10377
|
+
goal: exports_external.string().optional().describe("Goal or purpose of this recording session (e.g. 'code review for PR #123')"),
|
|
10378
|
+
role: exports_external.string().optional().describe("Agent role for this session (e.g. 'dev agent for connectdev')"),
|
|
10379
|
+
task_list_id: exports_external.string().optional().describe("Task list ID to bind this recording to"),
|
|
10380
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
10381
|
+
}, async (args) => {
|
|
10382
|
+
try {
|
|
10383
|
+
let processedText;
|
|
10384
|
+
let mode = "raw";
|
|
10385
|
+
let enhModel;
|
|
10386
|
+
if (args.enhance !== false) {
|
|
10387
|
+
const processed = await processText(args.text, config);
|
|
10388
|
+
if (processed.mode === "enhanced") {
|
|
10389
|
+
processedText = processed.text;
|
|
10390
|
+
mode = "enhanced";
|
|
10391
|
+
enhModel = processed.enhancement_model || undefined;
|
|
10392
|
+
}
|
|
10393
|
+
}
|
|
10394
|
+
const recording = createRecording({
|
|
10395
|
+
raw_text: args.text,
|
|
10396
|
+
processed_text: processedText,
|
|
10397
|
+
processing_mode: mode,
|
|
10398
|
+
model_used: "direct-input",
|
|
10399
|
+
enhancement_model: enhModel,
|
|
10400
|
+
tags: args.tags,
|
|
10401
|
+
agent_id: args.agent_id,
|
|
10402
|
+
project_id: args.project_id,
|
|
10403
|
+
session_id: args.session_id,
|
|
10404
|
+
goal: args.goal,
|
|
10405
|
+
role: args.role,
|
|
10406
|
+
task_list_id: args.task_list_id,
|
|
10407
|
+
metadata: args.metadata
|
|
10408
|
+
});
|
|
10409
|
+
const output = processedText || args.text;
|
|
10410
|
+
return text2(`${recording.id.slice(0, 8)} | ${mode} | ${output}`);
|
|
10411
|
+
} catch (e) {
|
|
10412
|
+
return errorResult(e);
|
|
10413
|
+
}
|
|
10414
|
+
});
|
|
10415
|
+
registerTool("get_recording", "Get recording by ID or prefix.", { id: exports_external.string() }, async (args) => {
|
|
10416
|
+
try {
|
|
10417
|
+
const r = getRecording(args.id);
|
|
10418
|
+
if (!r)
|
|
10419
|
+
return text2(`Not found: ${args.id}`);
|
|
10420
|
+
return text2(full(r));
|
|
10421
|
+
} catch (e) {
|
|
10422
|
+
return errorResult(e);
|
|
10423
|
+
}
|
|
10424
|
+
});
|
|
10425
|
+
registerTool("list_recordings", "List recordings. Compact default, recent first.", {
|
|
10426
|
+
limit: exports_external.number().optional(),
|
|
10427
|
+
offset: exports_external.number().optional(),
|
|
10428
|
+
processing_mode: exports_external.enum(["raw", "enhanced"]).optional(),
|
|
10429
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10430
|
+
search: exports_external.string().optional(),
|
|
10431
|
+
since: exports_external.string().optional(),
|
|
10432
|
+
until: exports_external.string().optional(),
|
|
10433
|
+
agent_id: exports_external.string().optional(),
|
|
10434
|
+
project_id: exports_external.string().optional(),
|
|
10435
|
+
session_id: exports_external.string().optional(),
|
|
10436
|
+
full: exports_external.boolean().optional()
|
|
10437
|
+
}, async (args) => {
|
|
10438
|
+
try {
|
|
10439
|
+
const filter = {
|
|
10440
|
+
limit: args.limit || 10,
|
|
10441
|
+
offset: args.offset,
|
|
10442
|
+
processing_mode: args.processing_mode,
|
|
10443
|
+
tags: args.tags,
|
|
10444
|
+
search: args.search,
|
|
10445
|
+
since: args.since,
|
|
10446
|
+
until: args.until,
|
|
10447
|
+
agent_id: args.agent_id,
|
|
10448
|
+
project_id: args.project_id,
|
|
10449
|
+
session_id: args.session_id
|
|
10450
|
+
};
|
|
10451
|
+
const recordings = listRecordings(filter);
|
|
10452
|
+
if (recordings.length === 0)
|
|
10453
|
+
return text2("No recordings found.");
|
|
10454
|
+
const fmt = args.full ? full : compact;
|
|
10455
|
+
const sep = args.full ? `
|
|
10360
10456
|
---
|
|
10361
10457
|
` : `
|
|
10362
10458
|
`;
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
});
|
|
10368
|
-
registerTool("search_recordings", "Search recordings by text.", {
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
}, async (args) => {
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10459
|
+
return text2(`${recordings.length} recording(s):${sep}${recordings.map(fmt).join(sep)}`);
|
|
10460
|
+
} catch (e) {
|
|
10461
|
+
return errorResult(e);
|
|
10462
|
+
}
|
|
10463
|
+
});
|
|
10464
|
+
registerTool("search_recordings", "Search recordings by text.", {
|
|
10465
|
+
query: exports_external.string(),
|
|
10466
|
+
limit: exports_external.number().optional(),
|
|
10467
|
+
agent_id: exports_external.string().optional(),
|
|
10468
|
+
project_id: exports_external.string().optional(),
|
|
10469
|
+
full: exports_external.boolean().optional()
|
|
10470
|
+
}, async (args) => {
|
|
10471
|
+
try {
|
|
10472
|
+
const results = searchRecordings(args.query, {
|
|
10473
|
+
limit: args.limit || 10,
|
|
10474
|
+
agent_id: args.agent_id,
|
|
10475
|
+
project_id: args.project_id
|
|
10476
|
+
});
|
|
10477
|
+
if (results.length === 0)
|
|
10478
|
+
return text2("No results.");
|
|
10479
|
+
const fmt = args.full ? full : compact;
|
|
10480
|
+
const sep = args.full ? `
|
|
10385
10481
|
---
|
|
10386
10482
|
` : `
|
|
10387
10483
|
`;
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
});
|
|
10393
|
-
registerTool("delete_recording", "Delete recording by ID.", { id: exports_external.string() }, async (args) => {
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
});
|
|
10400
|
-
registerTool("recording_stats", "Recording stats: count, modes, duration.", {}, async () => {
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10484
|
+
return text2(`${results.length} result(s):${sep}${results.map(fmt).join(sep)}`);
|
|
10485
|
+
} catch (e) {
|
|
10486
|
+
return errorResult(e);
|
|
10487
|
+
}
|
|
10488
|
+
});
|
|
10489
|
+
registerTool("delete_recording", "Delete recording by ID.", { id: exports_external.string() }, async (args) => {
|
|
10490
|
+
try {
|
|
10491
|
+
return text2(deleteRecording(args.id) ? `Deleted ${args.id}` : `Not found: ${args.id}`);
|
|
10492
|
+
} catch (e) {
|
|
10493
|
+
return errorResult(e);
|
|
10494
|
+
}
|
|
10495
|
+
});
|
|
10496
|
+
registerTool("recording_stats", "Recording stats: count, modes, duration.", {}, async () => {
|
|
10497
|
+
try {
|
|
10498
|
+
const s = getRecordingStats();
|
|
10499
|
+
let out = `Total: ${s.total} | Raw: ${s.raw} | Enhanced: ${s.enhanced} | Duration: ${(s.total_duration_ms / 1000).toFixed(1)}s`;
|
|
10500
|
+
if (Object.keys(s.by_model).length > 0) {
|
|
10501
|
+
out += `
|
|
10406
10502
|
` + Object.entries(s.by_model).map(([m, c]) => `${m}: ${c}`).join(", ");
|
|
10503
|
+
}
|
|
10504
|
+
return text2(out);
|
|
10505
|
+
} catch (e) {
|
|
10506
|
+
return errorResult(e);
|
|
10407
10507
|
}
|
|
10408
|
-
|
|
10409
|
-
}
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
});
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
}
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
});
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
}
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
const agents = listAgents();
|
|
10432
|
-
if (agents.length === 0)
|
|
10433
|
-
return text2("None.");
|
|
10434
|
-
return text2(agents.map((a) => `${a.id} | ${a.name} | ${a.role}`).join(`
|
|
10508
|
+
});
|
|
10509
|
+
registerTool("detect_enhancement", "Check if text needs AI enhancement.", { text: exports_external.string() }, async (args) => {
|
|
10510
|
+
try {
|
|
10511
|
+
const r = needsEnhancement(args.text, config);
|
|
10512
|
+
return text2(`${r.needs ? "Yes" : "No"}: ${r.reason}`);
|
|
10513
|
+
} catch (e) {
|
|
10514
|
+
return errorResult(e);
|
|
10515
|
+
}
|
|
10516
|
+
});
|
|
10517
|
+
registerTool("register_agent", "Register agent (idempotent).", { name: exports_external.string(), description: exports_external.string().optional(), role: exports_external.string().optional() }, async (args) => {
|
|
10518
|
+
try {
|
|
10519
|
+
const a = registerAgent(args.name, args.description, args.role);
|
|
10520
|
+
return text2(`${a.id} | ${a.name} | ${a.role}`);
|
|
10521
|
+
} catch (e) {
|
|
10522
|
+
return errorResult(e);
|
|
10523
|
+
}
|
|
10524
|
+
});
|
|
10525
|
+
registerTool("list_agents", "List registered agents.", {}, async () => {
|
|
10526
|
+
try {
|
|
10527
|
+
const agents = listAgents();
|
|
10528
|
+
if (agents.length === 0)
|
|
10529
|
+
return text2("None.");
|
|
10530
|
+
return text2(agents.map((a) => `${a.id} | ${a.name} | ${a.role}`).join(`
|
|
10435
10531
|
`));
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
});
|
|
10440
|
-
registerTool("get_agent", "Get agent by ID or name.", { id: exports_external.string() }, async (args) => {
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
});
|
|
10450
|
-
registerTool("register_project", "Register project (idempotent).", { name: exports_external.string(), path: exports_external.string(), description: exports_external.string().optional() }, async (args) => {
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
});
|
|
10458
|
-
registerTool("list_projects", "List registered projects.", {}, async () => {
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10532
|
+
} catch (e) {
|
|
10533
|
+
return errorResult(e);
|
|
10534
|
+
}
|
|
10535
|
+
});
|
|
10536
|
+
registerTool("get_agent", "Get agent by ID or name.", { id: exports_external.string() }, async (args) => {
|
|
10537
|
+
try {
|
|
10538
|
+
const a = getAgent(args.id);
|
|
10539
|
+
if (!a)
|
|
10540
|
+
return text2(`Not found: ${args.id}`);
|
|
10541
|
+
return text2(`${a.id} | ${a.name} | ${a.role} | ${a.last_seen_at}`);
|
|
10542
|
+
} catch (e) {
|
|
10543
|
+
return errorResult(e);
|
|
10544
|
+
}
|
|
10545
|
+
});
|
|
10546
|
+
registerTool("register_project", "Register project (idempotent).", { name: exports_external.string(), path: exports_external.string(), description: exports_external.string().optional() }, async (args) => {
|
|
10547
|
+
try {
|
|
10548
|
+
const p = registerProject(args.name, args.path, args.description);
|
|
10549
|
+
return text2(`${p.id.slice(0, 8)} | ${p.name} | ${p.path}`);
|
|
10550
|
+
} catch (e) {
|
|
10551
|
+
return errorResult(e);
|
|
10552
|
+
}
|
|
10553
|
+
});
|
|
10554
|
+
registerTool("list_projects", "List registered projects.", {}, async () => {
|
|
10555
|
+
try {
|
|
10556
|
+
const projects = listProjects();
|
|
10557
|
+
if (projects.length === 0)
|
|
10558
|
+
return text2("None.");
|
|
10559
|
+
return text2(projects.map((p) => `${p.id.slice(0, 8)} | ${p.name} | ${p.path}`).join(`
|
|
10464
10560
|
`));
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
});
|
|
10469
|
-
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) => {
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
});
|
|
10479
|
-
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) => {
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10561
|
+
} catch (e) {
|
|
10562
|
+
return errorResult(e);
|
|
10563
|
+
}
|
|
10564
|
+
});
|
|
10565
|
+
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) => {
|
|
10566
|
+
try {
|
|
10567
|
+
const agent = heartbeatAgent(args.agent_id);
|
|
10568
|
+
if (!agent)
|
|
10569
|
+
return text2(`Agent not found: ${args.agent_id}`);
|
|
10570
|
+
return text2(`${agent.id} | ${agent.name} | last_seen: ${agent.last_seen_at}`);
|
|
10571
|
+
} catch (e) {
|
|
10572
|
+
return errorResult(e);
|
|
10573
|
+
}
|
|
10574
|
+
});
|
|
10575
|
+
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) => {
|
|
10576
|
+
try {
|
|
10577
|
+
const agent = setAgentFocus(args.agent_id, args.project_id ?? null);
|
|
10578
|
+
if (!agent)
|
|
10579
|
+
return text2(`Agent not found: ${args.agent_id}`);
|
|
10580
|
+
return text2(args.project_id ? `Focus set: ${args.project_id}` : "Focus cleared");
|
|
10581
|
+
} catch (e) {
|
|
10582
|
+
return errorResult(e);
|
|
10583
|
+
}
|
|
10584
|
+
});
|
|
10585
|
+
registerTool("send_feedback", "Send feedback about this service", {
|
|
10586
|
+
message: exports_external.string().describe("Feedback message"),
|
|
10587
|
+
email: exports_external.string().optional().describe("Contact email (optional)"),
|
|
10588
|
+
category: exports_external.enum(["bug", "feature", "general"]).optional().describe("Feedback category")
|
|
10589
|
+
}, async (params) => {
|
|
10590
|
+
const adapter = getAdapter();
|
|
10591
|
+
const pkg = require_package();
|
|
10592
|
+
adapter.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", params.message, params.email || null, params.category || "general", pkg.version);
|
|
10593
|
+
return text2("Feedback saved. Thank you!");
|
|
10594
|
+
});
|
|
10595
|
+
registerRecordingsStorageTools(server);
|
|
10596
|
+
return server;
|
|
10597
|
+
}
|
|
10598
|
+
async function main() {
|
|
10599
|
+
const args = process.argv.slice(2);
|
|
10600
|
+
if (isStdioMode(args)) {
|
|
10601
|
+
const transport = new StdioServerTransport;
|
|
10602
|
+
await buildServer().connect(transport);
|
|
10603
|
+
return;
|
|
10487
10604
|
}
|
|
10488
|
-
});
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
adapter.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", params.message, params.email || null, params.category || "general", pkg.version);
|
|
10497
|
-
return text2("Feedback saved. Thank you!");
|
|
10498
|
-
});
|
|
10499
|
-
var transport = new StdioServerTransport;
|
|
10500
|
-
registerRecordingsCloudTools(server);
|
|
10501
|
-
await server.connect(transport);
|
|
10605
|
+
startMcpHttpServer({ name: "recordings", port: resolveMcpHttpPort(args), buildServer });
|
|
10606
|
+
}
|
|
10607
|
+
if (import.meta.main) {
|
|
10608
|
+
await main();
|
|
10609
|
+
}
|
|
10610
|
+
export {
|
|
10611
|
+
buildServer
|
|
10612
|
+
};
|