@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.
Files changed (48) hide show
  1. package/LICENSE +191 -170
  2. package/README.md +16 -7
  3. package/dist/cli/index.js +119 -65
  4. package/dist/cli/storage.d.ts +3 -0
  5. package/dist/cli/storage.d.ts.map +1 -0
  6. package/dist/db/pg-migrations.d.ts +1 -1
  7. package/dist/db/storage-config.d.ts +27 -0
  8. package/dist/db/storage-config.d.ts.map +1 -0
  9. package/dist/db/storage-sync.d.ts +36 -0
  10. package/dist/db/storage-sync.d.ts.map +1 -0
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +110 -50
  14. package/dist/lib/enhancer.d.ts.map +1 -1
  15. package/dist/lib/transcriber.d.ts +1 -0
  16. package/dist/lib/transcriber.d.ts.map +1 -1
  17. package/dist/mcp/http.d.ts +13 -0
  18. package/dist/mcp/http.d.ts.map +1 -0
  19. package/dist/mcp/index.d.ts +2 -1
  20. package/dist/mcp/index.d.ts.map +1 -1
  21. package/dist/mcp/index.js +519 -408
  22. package/dist/mcp/storage-tools.d.ts +3 -0
  23. package/dist/mcp/storage-tools.d.ts.map +1 -0
  24. package/dist/storage.d.ts +7 -0
  25. package/dist/storage.d.ts.map +1 -0
  26. package/dist/storage.js +5656 -0
  27. package/dist/version.d.ts +1 -1
  28. package/package.json +8 -3
  29. package/scripts/install_macos_app.sh +17 -0
  30. package/src/native/Recordings/App/RecordingsApp.swift +3 -2
  31. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +2 -0
  32. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +101 -80
  33. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +26 -0
  34. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +13 -1
  35. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +103 -31
  36. package/src/native/Recordings/RecordingsLib/SettingsView.swift +5 -1
  37. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +12 -0
  38. package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +49 -0
  39. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +54 -0
  40. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +58 -0
  41. package/dist/cli/cloud.d.ts +0 -3
  42. package/dist/cli/cloud.d.ts.map +0 -1
  43. package/dist/db/cloud-config.d.ts +0 -14
  44. package/dist/db/cloud-config.d.ts.map +0 -1
  45. package/dist/db/cloud-sync.d.ts +0 -29
  46. package/dist/db/cloud-sync.d.ts.map +0 -1
  47. package/dist/mcp/cloud-tools.d.ts +0 -3
  48. package/dist/mcp/cloud-tools.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -5098,7 +5098,7 @@ var init_pg_migrate = __esm(() => {
5098
5098
  var require_package = __commonJS((exports, module) => {
5099
5099
  module.exports = {
5100
5100
  name: "@hasna/recordings",
5101
- version: "0.1.23",
5101
+ version: "0.1.25",
5102
5102
  type: "module",
5103
5103
  description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
5104
5104
  repository: {
@@ -5115,13 +5115,18 @@ var require_package = __commonJS((exports, module) => {
5115
5115
  ".": {
5116
5116
  import: "./dist/index.js",
5117
5117
  types: "./dist/index.d.ts"
5118
+ },
5119
+ "./storage": {
5120
+ import: "./dist/storage.js",
5121
+ types: "./dist/storage.d.ts"
5118
5122
  }
5119
5123
  },
5120
5124
  scripts: {
5121
- build: "bun run build:cli && bun run build:mcp && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
5125
+ clean: "rm -rf dist",
5126
+ build: "bun run clean && bun run build:cli && bun run build:mcp && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
5122
5127
  "build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
5123
5128
  "build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
5124
- "build:lib": "bun build src/index.ts --target=bun --outfile=dist/index.js --external=openai",
5129
+ "build:lib": "bun build src/index.ts src/storage.ts --target=bun --outdir=dist --external=openai",
5125
5130
  typecheck: "tsc --noEmit",
5126
5131
  test: "bun test",
5127
5132
  "test:coverage": "bun test --coverage",
@@ -5889,7 +5894,7 @@ async function transcribeAudio(audioPath, config, options = {}) {
5889
5894
  };
5890
5895
  } catch (error) {
5891
5896
  const msg = error instanceof Error ? error.message : String(error);
5892
- throw new TranscriptionError(`Transcription failed: ${msg}`);
5897
+ throw new TranscriptionError(`Transcription failed: ${describeTranscriptionFailure(msg)}`);
5893
5898
  }
5894
5899
  }
5895
5900
  async function transcribeAudioStream(audioPath, config, options = {}) {
@@ -5925,11 +5930,20 @@ async function transcribeAudioStream(audioPath, config, options = {}) {
5925
5930
  };
5926
5931
  } catch (error) {
5927
5932
  const msg = error instanceof Error ? error.message : String(error);
5928
- throw new TranscriptionError(`Streaming transcription failed: ${msg}`);
5933
+ throw new TranscriptionError(`Streaming transcription failed: ${describeTranscriptionFailure(msg)}`);
5929
5934
  } finally {
5930
5935
  fileStream.destroy();
5931
5936
  }
5932
5937
  }
5938
+ function describeTranscriptionFailure(message) {
5939
+ if (/401|incorrect api key|invalid_api_key/i.test(message)) {
5940
+ 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.";
5941
+ }
5942
+ if (/429|exceeded your current quota|insufficient_quota/i.test(message)) {
5943
+ return "OpenAI quota exceeded (429). Check the OpenAI account plan and billing.";
5944
+ }
5945
+ return message;
5946
+ }
5933
5947
  function buildVerbatimPrompt(context) {
5934
5948
  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.";
5935
5949
  const trimmed = context?.trim();
@@ -6047,7 +6061,7 @@ ${systemPrompt}` : basePrompt;
6047
6061
  };
6048
6062
  } catch (error) {
6049
6063
  const msg = error instanceof Error ? error.message : String(error);
6050
- throw new EnhancementError(`Enhancement failed: ${msg}`);
6064
+ throw new EnhancementError(`Enhancement failed: ${describeTranscriptionFailure(msg)}`);
6051
6065
  }
6052
6066
  }
6053
6067
  async function processText(rawText, config, systemPrompt) {
@@ -6067,69 +6081,94 @@ async function processText(rawText, config, systemPrompt) {
6067
6081
  }
6068
6082
 
6069
6083
  // src/version.ts
6070
- var VERSION = "0.1.20";
6084
+ var VERSION = "0.1.25";
6071
6085
 
6072
- // src/cli/cloud.ts
6086
+ // src/cli/storage.ts
6073
6087
  import chalk from "chalk";
6074
6088
 
6075
- // src/db/cloud-config.ts
6089
+ // src/db/storage-config.ts
6076
6090
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
6077
6091
  import { homedir as homedir2 } from "os";
6078
6092
  import { join as join3 } from "path";
6079
- var CONFIG_PATH = join3(homedir2(), ".hasna", "recordings", "cloud", "config.json");
6080
- function isMode(value) {
6081
- return value === "local" || value === "hybrid" || value === "cloud";
6093
+ var STORAGE_CONFIG_PATH = join3(homedir2(), ".hasna", "recordings", "storage", "config.json");
6094
+ var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
6095
+ var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
6096
+ var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
6097
+ var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
6098
+ var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
6099
+ function readEnv(name) {
6100
+ const value = process.env[name]?.trim();
6101
+ return value || undefined;
6102
+ }
6103
+ function normalizeMode(value) {
6104
+ const normalized = value?.trim().toLowerCase();
6105
+ if (normalized === "local" || normalized === "hybrid" || normalized === "remote")
6106
+ return normalized;
6107
+ return;
6082
6108
  }
6083
- function envConnectionString() {
6084
- return process.env["HASNA_RECORDINGS_CLOUD_DATABASE_URL"] ?? process.env["OPEN_RECORDINGS_CLOUD_DATABASE_URL"] ?? process.env["RECORDINGS_CLOUD_DATABASE_URL"];
6109
+ function getStorageDatabaseEnvName() {
6110
+ for (const name of STORAGE_DATABASE_ENV) {
6111
+ if (readEnv(name))
6112
+ return name;
6113
+ }
6114
+ return null;
6115
+ }
6116
+ function getStorageDatabaseEnv() {
6117
+ const name = getStorageDatabaseEnvName();
6118
+ return name ? { name } : null;
6085
6119
  }
6086
- function getCloudConfig() {
6120
+ function getStorageDatabaseUrl() {
6121
+ const env = getStorageDatabaseEnv();
6122
+ return env ? readEnv(env.name) : undefined;
6123
+ }
6124
+ function getStorageConfig() {
6087
6125
  const config = {
6088
6126
  mode: "local",
6089
6127
  rds: {
6090
6128
  host: "",
6091
6129
  port: 5432,
6092
6130
  username: "",
6093
- password_env: "RECORDINGS_CLOUD_DATABASE_PASSWORD",
6131
+ password_env: "RECORDINGS_DATABASE_PASSWORD",
6094
6132
  ssl: true
6095
6133
  }
6096
6134
  };
6097
- if (existsSync3(CONFIG_PATH)) {
6135
+ if (existsSync3(STORAGE_CONFIG_PATH)) {
6098
6136
  try {
6099
- const raw = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
6100
- config.mode = raw.mode ?? config.mode;
6137
+ const raw = JSON.parse(readFileSync2(STORAGE_CONFIG_PATH, "utf-8"));
6138
+ config.mode = normalizeMode(raw.mode) ?? config.mode;
6101
6139
  config.rds = { ...config.rds, ...raw.rds ?? {} };
6102
6140
  } catch {}
6103
6141
  }
6104
- const modeOverride = process.env["HASNA_RECORDINGS_CLOUD_MODE"] ?? process.env["OPEN_RECORDINGS_CLOUD_MODE"] ?? process.env["RECORDINGS_CLOUD_MODE"];
6105
- if (isMode(modeOverride)) {
6106
- config.mode = modeOverride;
6107
- } else if (envConnectionString() && config.mode === "local") {
6142
+ const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
6143
+ const normalizedMode = normalizeMode(modeOverride);
6144
+ if (normalizedMode) {
6145
+ config.mode = normalizedMode;
6146
+ } else if (getStorageDatabaseUrl() && config.mode === "local") {
6108
6147
  config.mode = "hybrid";
6109
6148
  }
6110
6149
  return config;
6111
6150
  }
6112
- function getConnectionString(dbName = "recordings") {
6113
- const direct = envConnectionString();
6151
+ function getStorageConnectionString(dbName = "recordings") {
6152
+ const direct = getStorageDatabaseUrl();
6114
6153
  if (direct)
6115
6154
  return direct;
6116
- const config = getCloudConfig();
6155
+ const config = getStorageConfig();
6117
6156
  const { host, port, username, password_env, ssl } = config.rds;
6118
6157
  if (!host || !username) {
6119
- throw new Error("Cloud database is not configured. Set HASNA_RECORDINGS_CLOUD_DATABASE_URL or configure ~/.hasna/recordings/cloud/config.json.");
6158
+ throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
6120
6159
  }
6121
6160
  const password = process.env[password_env];
6122
6161
  if (!password) {
6123
- throw new Error(`Cloud database password is not set. Export ${password_env}.`);
6162
+ throw new Error(`Storage database password is not set. Export ${password_env}.`);
6124
6163
  }
6125
6164
  const sslParam = ssl ? "?sslmode=require" : "";
6126
6165
  return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
6127
6166
  }
6128
6167
 
6129
- // src/db/cloud-sync.ts
6168
+ // src/db/storage-sync.ts
6130
6169
  init_remote_storage();
6131
6170
  init_pg_migrations();
6132
- var CLOUD_TABLES = [
6171
+ var STORAGE_TABLES = [
6133
6172
  "projects",
6134
6173
  "agents",
6135
6174
  "recordings",
@@ -6188,21 +6227,26 @@ function upsertSqlite(db, table, rows) {
6188
6227
  }
6189
6228
  return written;
6190
6229
  }
6191
- async function getCloudPg() {
6192
- return new PgAdapterAsync(getConnectionString("recordings"));
6230
+ async function getStoragePg() {
6231
+ return new PgAdapterAsync(getStorageConnectionString("recordings"));
6193
6232
  }
6194
- async function runCloudMigrations(remote) {
6233
+ async function runStorageMigrations(remote) {
6195
6234
  for (const migration of PG_MIGRATIONS) {
6196
6235
  await remote.exec(migration);
6197
6236
  }
6198
6237
  }
6199
- function getCloudStatus(db = getDatabase()) {
6200
- const config = getCloudConfig();
6238
+ function getStorageStatus(db = getDatabase()) {
6239
+ const config = getStorageConfig();
6240
+ const activeEnv = getStorageDatabaseEnv();
6201
6241
  return {
6242
+ configured: Boolean(activeEnv),
6202
6243
  mode: config.mode,
6203
- enabled: config.mode === "hybrid" || config.mode === "cloud",
6244
+ enabled: config.mode === "hybrid" || config.mode === "remote",
6245
+ env: STORAGE_DATABASE_ENV,
6246
+ activeEnv: activeEnv?.name ?? null,
6247
+ service: "recordings",
6204
6248
  db_path: getDbPath(),
6205
- tables: CLOUD_TABLES.map((table) => {
6249
+ tables: STORAGE_TABLES.map((table) => {
6206
6250
  try {
6207
6251
  const row = db.query(`SELECT COUNT(*) as count FROM ${quoteId(table)}`).get();
6208
6252
  return { table, rows: row.count };
@@ -6212,12 +6256,12 @@ function getCloudStatus(db = getDatabase()) {
6212
6256
  })
6213
6257
  };
6214
6258
  }
6215
- async function pushCloudChanges(tables = [...CLOUD_TABLES]) {
6259
+ async function pushStorageChanges(tables = [...STORAGE_TABLES]) {
6216
6260
  const db = getDatabase();
6217
- const remote = await getCloudPg();
6261
+ const remote = await getStoragePg();
6218
6262
  const results = [];
6219
6263
  try {
6220
- await runCloudMigrations(remote);
6264
+ await runStorageMigrations(remote);
6221
6265
  for (const table of tables) {
6222
6266
  const result = { table, direction: "push", rows_read: 0, rows_written: 0, errors: [] };
6223
6267
  try {
@@ -6234,12 +6278,12 @@ async function pushCloudChanges(tables = [...CLOUD_TABLES]) {
6234
6278
  }
6235
6279
  return results;
6236
6280
  }
6237
- async function pullCloudChanges(tables = [...CLOUD_TABLES]) {
6281
+ async function pullStorageChanges(tables = [...STORAGE_TABLES]) {
6238
6282
  const db = getDatabase();
6239
- const remote = await getCloudPg();
6283
+ const remote = await getStoragePg();
6240
6284
  const results = [];
6241
6285
  try {
6242
- await runCloudMigrations(remote);
6286
+ await runStorageMigrations(remote);
6243
6287
  for (const table of tables) {
6244
6288
  const result = { table, direction: "pull", rows_read: 0, rows_written: 0, errors: [] };
6245
6289
  try {
@@ -6256,20 +6300,26 @@ async function pullCloudChanges(tables = [...CLOUD_TABLES]) {
6256
6300
  }
6257
6301
  return results;
6258
6302
  }
6259
- async function syncCloudChanges(tables = [...CLOUD_TABLES]) {
6303
+ async function syncStorageChanges(tables = [...STORAGE_TABLES]) {
6260
6304
  return {
6261
- push: await pushCloudChanges(tables),
6262
- pull: await pullCloudChanges(tables)
6305
+ push: await pushStorageChanges(tables),
6306
+ pull: await pullStorageChanges(tables)
6263
6307
  };
6264
6308
  }
6265
- function parseCloudTables(raw) {
6309
+ function parseStorageTables(raw) {
6266
6310
  if (!raw)
6267
- return [...CLOUD_TABLES];
6311
+ return [...STORAGE_TABLES];
6268
6312
  const requested = raw.split(",").map((table) => table.trim()).filter(Boolean);
6269
- return requested.length > 0 ? requested : [...CLOUD_TABLES];
6313
+ if (requested.length === 0)
6314
+ return [...STORAGE_TABLES];
6315
+ const allowed = new Set(STORAGE_TABLES);
6316
+ const invalid = requested.filter((table) => !allowed.has(table));
6317
+ if (invalid.length > 0)
6318
+ throw new Error(`Unknown recordings sync table(s): ${invalid.join(", ")}`);
6319
+ return requested;
6270
6320
  }
6271
6321
 
6272
- // src/cli/cloud.ts
6322
+ // src/cli/storage.ts
6273
6323
  function shouldJson(program, opts) {
6274
6324
  return Boolean(opts.json || program.opts().json);
6275
6325
  }
@@ -6285,10 +6335,10 @@ function printResults(results) {
6285
6335
  }
6286
6336
  }
6287
6337
  }
6288
- function registerCloudCommands(program) {
6289
- const cloud = program.command("cloud").description("Manage recordings local/remote cloud sync");
6290
- cloud.command("status").description("Show local database and cloud sync status").option("--json", "Output JSON").action((opts) => {
6291
- const status = getCloudStatus();
6338
+ function registerStorageCommands(program) {
6339
+ const storage = program.command("storage").description("Manage recordings local/remote storage sync");
6340
+ storage.command("status").description("Show local database and storage sync status").option("--json", "Output JSON").action((opts) => {
6341
+ const status = getStorageStatus();
6292
6342
  if (shouldJson(program, opts)) {
6293
6343
  printJson(status);
6294
6344
  return;
@@ -6300,9 +6350,9 @@ function registerCloudCommands(program) {
6300
6350
  console.log(` ${table.table}: ${table.rows}`);
6301
6351
  }
6302
6352
  });
6303
- cloud.command("push").description("Push local recordings data to PostgreSQL").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6353
+ storage.command("push").description("Push local recordings data to PostgreSQL").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6304
6354
  try {
6305
- const results = await pushCloudChanges(parseCloudTables(opts.tables));
6355
+ const results = await pushStorageChanges(parseStorageTables(opts.tables));
6306
6356
  if (shouldJson(program, opts))
6307
6357
  printJson(results);
6308
6358
  else
@@ -6315,9 +6365,9 @@ function registerCloudCommands(program) {
6315
6365
  process.exitCode = 1;
6316
6366
  }
6317
6367
  });
6318
- cloud.command("pull").description("Pull PostgreSQL recordings data into the local database").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6368
+ storage.command("pull").description("Pull PostgreSQL recordings data into the local database").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6319
6369
  try {
6320
- const results = await pullCloudChanges(parseCloudTables(opts.tables));
6370
+ const results = await pullStorageChanges(parseStorageTables(opts.tables));
6321
6371
  if (shouldJson(program, opts))
6322
6372
  printJson(results);
6323
6373
  else
@@ -6330,9 +6380,9 @@ function registerCloudCommands(program) {
6330
6380
  process.exitCode = 1;
6331
6381
  }
6332
6382
  });
6333
- cloud.command("sync").description("Push local changes, then pull remote changes").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6383
+ storage.command("sync").description("Push local changes, then pull remote changes").option("--tables <tables>", "Comma-separated table names").option("--json", "Output JSON").action(async (opts) => {
6334
6384
  try {
6335
- const result = await syncCloudChanges(parseCloudTables(opts.tables));
6385
+ const result = await syncStorageChanges(parseStorageTables(opts.tables));
6336
6386
  if (shouldJson(program, opts)) {
6337
6387
  printJson(result);
6338
6388
  return;
@@ -6349,10 +6399,10 @@ function registerCloudCommands(program) {
6349
6399
  process.exitCode = 1;
6350
6400
  }
6351
6401
  });
6352
- cloud.command("migrate").description("Apply PostgreSQL migrations").option("--connection-string <url>", "PostgreSQL connection string").option("--json", "Output JSON").action(async (opts) => {
6402
+ storage.command("migrate").description("Apply PostgreSQL migrations").option("--connection-string <url>", "PostgreSQL connection string").option("--json", "Output JSON").action(async (opts) => {
6353
6403
  try {
6354
6404
  const { applyPgMigrations: applyPgMigrations2 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
6355
- const result = await applyPgMigrations2(opts.connectionString || getConnectionString("recordings"));
6405
+ const result = await applyPgMigrations2(opts.connectionString || getStorageConnectionString("recordings"));
6356
6406
  if (shouldJson(program, opts)) {
6357
6407
  printJson(result);
6358
6408
  return;
@@ -6374,7 +6424,7 @@ function registerCloudCommands(program) {
6374
6424
  process.exitCode = 1;
6375
6425
  }
6376
6426
  });
6377
- cloud.command("feedback <message>").description("Save feedback locally").option("--email <email>").option("--category <category>", "Feedback category", "general").option("--json", "Output JSON").action((message, opts) => {
6427
+ storage.command("feedback <message>").description("Save feedback locally").option("--email <email>").option("--category <category>", "Feedback category", "general").option("--json", "Output JSON").action((message, opts) => {
6378
6428
  try {
6379
6429
  const adapter = getAdapter();
6380
6430
  adapter.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", message, opts.email || null, opts.category || "general", "recordings");
@@ -6395,7 +6445,7 @@ function registerCloudCommands(program) {
6395
6445
  // src/cli/index.ts
6396
6446
  var program = new Command;
6397
6447
  program.name("recordings").description("Speech-to-text recording tool \u2014 record, transcribe, and enhance with AI").version(VERSION).option("--json", "Output as JSON").option("--agent <name>", "Agent name or ID").option("--project <name>", "Project name or ID").option("--session <id>", "Session ID");
6398
- registerCloudCommands(program);
6448
+ registerStorageCommands(program);
6399
6449
  program.command("record").description("Record from microphone, transcribe, and optionally enhance").option("-d, --duration <seconds>", "Record for specific duration").option("--no-enhance", "Skip AI enhancement").option("-t, --tags <tags>", "Comma-separated tags").option("-l, --language <lang>", "Language code (e.g. en, es, fr)").action(async (opts) => {
6400
6450
  const config = loadConfig();
6401
6451
  ensureDataDir(config);
@@ -7338,4 +7388,8 @@ function findPackageRoot() {
7338
7388
  current = parent;
7339
7389
  }
7340
7390
  }
7341
- program.parse();
7391
+ program.parseAsync().catch((error) => {
7392
+ const msg = error instanceof Error ? error.message : String(error);
7393
+ console.error(`ERROR: ${msg}`);
7394
+ process.exit(1);
7395
+ });
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerStorageCommands(program: Command): void;
3
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/cli/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkI9D"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PostgreSQL migrations for open-recordings cloud sync.
2
+ * PostgreSQL migrations for open-recordings storage sync.
3
3
  *
4
4
  * Equivalent to the SQLite schema in database.ts, translated for PostgreSQL.
5
5
  */
@@ -0,0 +1,27 @@
1
+ export type StorageMode = "local" | "hybrid" | "remote";
2
+ export interface StorageConfig {
3
+ mode: StorageMode;
4
+ rds: {
5
+ host: string;
6
+ port: number;
7
+ username: string;
8
+ password_env: string;
9
+ ssl: boolean;
10
+ };
11
+ }
12
+ export interface StorageEnv {
13
+ name: string;
14
+ }
15
+ export declare const RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
16
+ export declare const RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
17
+ export declare const RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
18
+ export declare const RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
19
+ export declare const STORAGE_DATABASE_ENV: readonly ["HASNA_RECORDINGS_DATABASE_URL", "RECORDINGS_DATABASE_URL"];
20
+ export declare const STORAGE_MODE_ENV: readonly ["HASNA_RECORDINGS_STORAGE_MODE", "RECORDINGS_STORAGE_MODE"];
21
+ export declare function getStorageDatabaseEnvName(): (typeof STORAGE_DATABASE_ENV)[number] | null;
22
+ export declare function getStorageDatabaseEnv(): StorageEnv | null;
23
+ export declare function getStorageDatabaseUrl(): string | undefined;
24
+ export declare function getStorageConfig(): StorageConfig;
25
+ export declare function getStorageConnectionString(dbName?: string): string;
26
+ export declare const getConnectionString: typeof getStorageConnectionString;
27
+ //# sourceMappingURL=storage-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-config.d.ts","sourceRoot":"","sources":["../../src/db/storage-config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,GAAG,EAAE,OAAO,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AACtE,eAAO,MAAM,+BAA+B,4BAA4B,CAAC;AACzE,eAAO,MAAM,2BAA2B,kCAAkC,CAAC;AAC3E,eAAO,MAAM,oCAAoC,4BAA4B,CAAC;AAC9E,eAAO,MAAM,oBAAoB,uEAAqE,CAAC;AACvG,eAAO,MAAM,gBAAgB,uEAA+E,CAAC;AAa7G,wBAAgB,yBAAyB,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAKxF;AAED,wBAAgB,qBAAqB,IAAI,UAAU,GAAG,IAAI,CAGzD;AAED,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAG1D;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CA+BhD;AAED,wBAAgB,0BAA0B,CAAC,MAAM,SAAe,GAAG,MAAM,CAiBxE;AAED,eAAO,MAAM,mBAAmB,mCAA6B,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type Database } from "bun:sqlite";
2
+ import { STORAGE_DATABASE_ENV, type StorageMode } from "./storage-config.js";
3
+ import { PgAdapterAsync } from "./remote-storage.js";
4
+ export interface SyncResult {
5
+ table: string;
6
+ direction: "push" | "pull";
7
+ rows_read: number;
8
+ rows_written: number;
9
+ errors: string[];
10
+ }
11
+ export interface StorageStatus {
12
+ configured: boolean;
13
+ mode: StorageMode;
14
+ enabled: boolean;
15
+ env: typeof STORAGE_DATABASE_ENV;
16
+ activeEnv: string | null;
17
+ service: "recordings";
18
+ db_path: string;
19
+ tables: Array<{
20
+ table: string;
21
+ rows: number;
22
+ }>;
23
+ }
24
+ export declare const STORAGE_TABLES: readonly ["projects", "agents", "recordings", "recording_tags", "feedback"];
25
+ export declare const RECORDINGS_STORAGE_TABLES: readonly ["projects", "agents", "recordings", "recording_tags", "feedback"];
26
+ export declare function getStoragePg(): Promise<PgAdapterAsync>;
27
+ export declare function runStorageMigrations(remote: PgAdapterAsync): Promise<void>;
28
+ export declare function getStorageStatus(db?: Database): StorageStatus;
29
+ export declare function pushStorageChanges(tables?: string[]): Promise<SyncResult[]>;
30
+ export declare function pullStorageChanges(tables?: string[]): Promise<SyncResult[]>;
31
+ export declare function syncStorageChanges(tables?: string[]): Promise<{
32
+ push: SyncResult[];
33
+ pull: SyncResult[];
34
+ }>;
35
+ export declare function parseStorageTables(raw?: string): string[];
36
+ //# sourceMappingURL=storage-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-sync.d.ts","sourceRoot":"","sources":["../../src/db/storage-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EACL,oBAAoB,EAIpB,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,oBAAoB,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,eAAO,MAAM,cAAc,6EAMjB,CAAC;AAEX,eAAO,MAAM,yBAAyB,6EAAiB,CAAC;AA6ExD,wBAAsB,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC,CAE5D;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,GAAE,QAAwB,GAAG,aAAa,CAoB5E;AAED,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,MAAM,EAAwB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAuBtG;AAED,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,MAAM,EAAwB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAuBtG;AAED,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,MAAM,EAAwB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAKpI;AAED,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAQzD"}
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export type { Recording, CreateRecordingInput, RecordingFilter, ProcessingMode,
2
2
  export { RecordingNotFoundError, RecordingError, TranscriptionError, EnhancementError, } from "./types/index.js";
3
3
  export { getDatabase, closeDatabase, resetDatabase, getDbPath, shortUuid, } from "./db/database.js";
4
4
  export { PgAdapterAsync } from "./db/remote-storage.js";
5
- export { getCloudConfig, getConnectionString } from "./db/cloud-config.js";
6
- export { getCloudStatus, pushCloudChanges, pullCloudChanges, syncCloudChanges, parseCloudTables, type CloudStatus, type SyncResult, } from "./db/cloud-sync.js";
5
+ export { RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV, RECORDINGS_STORAGE_MODE_ENV, RECORDINGS_STORAGE_MODE_FALLBACK_ENV, STORAGE_DATABASE_ENV, STORAGE_MODE_ENV, getStorageConfig, getStorageConnectionString, getConnectionString, getStorageDatabaseEnv, getStorageDatabaseEnvName, getStorageDatabaseUrl, type StorageConfig, type StorageEnv, type StorageMode, } from "./db/storage-config.js";
6
+ export { RECORDINGS_STORAGE_TABLES, STORAGE_TABLES, getStorageStatus, pushStorageChanges, pullStorageChanges, syncStorageChanges, parseStorageTables, type StorageStatus, type SyncResult, } from "./db/storage-sync.js";
7
7
  export { applyPgMigrations, type PgMigrationResult } from "./db/pg-migrate.js";
8
8
  export { createRecording, getRecording, listRecordings, deleteRecording, searchRecordings, getRecordingStats, } from "./db/recordings.js";
9
9
  export { registerAgent, getAgent, listAgents } from "./db/agents.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,KAAK,EACL,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EACL,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGrE,OAAO,EACL,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,KAAK,EACL,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAC/B,2BAA2B,EAC3B,oCAAoC,EACpC,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EACL,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGrE,OAAO,EACL,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}