@hasna/recordings 0.1.35 → 0.1.36

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/README.md CHANGED
@@ -49,6 +49,21 @@ This package has native local/remote sync. Local data stays in SQLite under
49
49
  `HASNA_RECORDINGS_DATABASE_URL` is set or `~/.hasna/recordings/storage/config.json` is
50
50
  configured.
51
51
 
52
+ The optional config file uses a `postgres` object:
53
+
54
+ ```json
55
+ {
56
+ "mode": "remote",
57
+ "postgres": {
58
+ "host": "db.example",
59
+ "port": 5432,
60
+ "username": "recordings",
61
+ "password_env": "RECORDINGS_DATABASE_PASSWORD",
62
+ "ssl": true
63
+ }
64
+ }
65
+ ```
66
+
52
67
  ```bash
53
68
  recordings storage status
54
69
  recordings storage migrate
@@ -57,6 +72,8 @@ recordings storage pull
57
72
  ```
58
73
 
59
74
  `RECORDINGS_DATABASE_URL` is accepted as the non-Hasna fallback database URL.
75
+ `HASNA_RECORDINGS_STORAGE_CONFIG` can point automation at a non-default storage
76
+ config file.
60
77
 
61
78
  ## Data Directory
62
79
 
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.35",
5101
+ version: "0.1.36",
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: {
@@ -5132,7 +5132,9 @@ var require_package = __commonJS((exports, module) => {
5132
5132
  "test:coverage": "bun test --coverage",
5133
5133
  "dev:cli": "bun run src/cli/index.ts",
5134
5134
  "dev:mcp": "bun run src/mcp/index.ts",
5135
- prepack: "bun run build",
5135
+ "verify:release": "bun run scripts/release-guard.ts",
5136
+ prepack: "bun run build && bun run verify:release",
5137
+ prepublishOnly: "bun run typecheck && bun run test",
5136
5138
  postinstall: "bash scripts/install_macos_app.sh --postinstall"
5137
5139
  },
5138
5140
  files: [
@@ -6989,7 +6991,7 @@ async function processText(rawText, config, systemPrompt) {
6989
6991
  }
6990
6992
 
6991
6993
  // src/version.ts
6992
- var VERSION = "0.1.35";
6994
+ var VERSION = "0.1.36";
6993
6995
 
6994
6996
  // src/cli/storage.ts
6995
6997
  import chalk from "chalk";
@@ -6998,11 +7000,12 @@ import chalk from "chalk";
6998
7000
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
6999
7001
  import { homedir as homedir3 } from "os";
7000
7002
  import { join as join4 } from "path";
7001
- var STORAGE_CONFIG_PATH = join4(homedir3(), ".hasna", "recordings", "storage", "config.json");
7003
+ var LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
7002
7004
  var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
7003
7005
  var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
7004
7006
  var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
7005
7007
  var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
7008
+ var RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
7006
7009
  var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
7007
7010
  function readEnv(name) {
7008
7011
  const value = process.env[name]?.trim();
@@ -7029,10 +7032,16 @@ function getStorageDatabaseUrl() {
7029
7032
  const env = getStorageDatabaseEnv();
7030
7033
  return env ? readEnv(env.name) : undefined;
7031
7034
  }
7035
+ function getStorageConfigPath() {
7036
+ const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
7037
+ if (override)
7038
+ return override;
7039
+ return join4(homedir3(), ".hasna", "recordings", "storage", "config.json");
7040
+ }
7032
7041
  function getStorageConfig() {
7033
7042
  const config = {
7034
7043
  mode: "local",
7035
- rds: {
7044
+ postgres: {
7036
7045
  host: "",
7037
7046
  port: 5432,
7038
7047
  username: "",
@@ -7040,11 +7049,13 @@ function getStorageConfig() {
7040
7049
  ssl: true
7041
7050
  }
7042
7051
  };
7043
- if (existsSync4(STORAGE_CONFIG_PATH)) {
7052
+ const storageConfigPath = getStorageConfigPath();
7053
+ if (existsSync4(storageConfigPath)) {
7044
7054
  try {
7045
- const raw = JSON.parse(readFileSync2(STORAGE_CONFIG_PATH, "utf-8"));
7055
+ const raw = JSON.parse(readFileSync2(storageConfigPath, "utf-8"));
7056
+ const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
7046
7057
  config.mode = normalizeMode(raw.mode) ?? config.mode;
7047
- config.rds = { ...config.rds, ...raw.rds ?? {} };
7058
+ config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
7048
7059
  } catch {}
7049
7060
  }
7050
7061
  const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
@@ -7061,7 +7072,7 @@ function getStorageConnectionString(dbName = "recordings") {
7061
7072
  if (direct)
7062
7073
  return direct;
7063
7074
  const config = getStorageConfig();
7064
- const { host, port, username, password_env, ssl } = config.rds;
7075
+ const { host, port, username, password_env, ssl } = config.postgres;
7065
7076
  if (!host || !username) {
7066
7077
  throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
7067
7078
  }
@@ -7146,8 +7157,9 @@ async function runStorageMigrations(remote) {
7146
7157
  function getStorageStatus(db = getDatabase()) {
7147
7158
  const config = getStorageConfig();
7148
7159
  const activeEnv = getStorageDatabaseEnv();
7160
+ const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
7149
7161
  return {
7150
- configured: Boolean(activeEnv),
7162
+ configured: Boolean(activeEnv) || hasConfiguredPostgres,
7151
7163
  mode: config.mode,
7152
7164
  enabled: config.mode === "hybrid" || config.mode === "remote",
7153
7165
  env: STORAGE_DATABASE_ENV,
@@ -1,7 +1,7 @@
1
1
  export type StorageMode = "local" | "hybrid" | "remote";
2
2
  export interface StorageConfig {
3
3
  mode: StorageMode;
4
- rds: {
4
+ postgres: {
5
5
  host: string;
6
6
  port: number;
7
7
  username: string;
@@ -16,6 +16,7 @@ export declare const RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
16
16
  export declare const RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
17
17
  export declare const RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
18
18
  export declare const RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
19
+ export declare const RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
19
20
  export declare const STORAGE_DATABASE_ENV: readonly ["HASNA_RECORDINGS_DATABASE_URL", "RECORDINGS_DATABASE_URL"];
20
21
  export declare const STORAGE_MODE_ENV: readonly ["HASNA_RECORDINGS_STORAGE_MODE", "RECORDINGS_STORAGE_MODE"];
21
22
  export declare function getStorageDatabaseEnvName(): (typeof STORAGE_DATABASE_ENV)[number] | null;
@@ -1 +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"}
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,QAAQ,EAAE;QACR,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,6BAA6B,oCAAoC,CAAC;AAC/E,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;AAQD,wBAAgB,gBAAgB,IAAI,aAAa,CAoChD;AAED,wBAAgB,0BAA0B,CAAC,MAAM,SAAe,GAAG,MAAM,CAiBxE;AAED,eAAO,MAAM,mBAAmB,mCAA6B,CAAC"}
@@ -1 +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"}
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,CAqB5E;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.js CHANGED
@@ -5371,11 +5371,12 @@ class PgAdapterAsync {
5371
5371
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5372
5372
  import { homedir as homedir2 } from "os";
5373
5373
  import { join as join2 } from "path";
5374
- var STORAGE_CONFIG_PATH = join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
5374
+ var LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
5375
5375
  var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
5376
5376
  var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
5377
5377
  var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
5378
5378
  var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
5379
+ var RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
5379
5380
  var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
5380
5381
  var STORAGE_MODE_ENV = [RECORDINGS_STORAGE_MODE_ENV, RECORDINGS_STORAGE_MODE_FALLBACK_ENV];
5381
5382
  function readEnv(name) {
@@ -5403,10 +5404,16 @@ function getStorageDatabaseUrl() {
5403
5404
  const env = getStorageDatabaseEnv();
5404
5405
  return env ? readEnv(env.name) : undefined;
5405
5406
  }
5407
+ function getStorageConfigPath() {
5408
+ const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
5409
+ if (override)
5410
+ return override;
5411
+ return join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
5412
+ }
5406
5413
  function getStorageConfig() {
5407
5414
  const config = {
5408
5415
  mode: "local",
5409
- rds: {
5416
+ postgres: {
5410
5417
  host: "",
5411
5418
  port: 5432,
5412
5419
  username: "",
@@ -5414,11 +5421,13 @@ function getStorageConfig() {
5414
5421
  ssl: true
5415
5422
  }
5416
5423
  };
5417
- if (existsSync2(STORAGE_CONFIG_PATH)) {
5424
+ const storageConfigPath = getStorageConfigPath();
5425
+ if (existsSync2(storageConfigPath)) {
5418
5426
  try {
5419
- const raw = JSON.parse(readFileSync2(STORAGE_CONFIG_PATH, "utf-8"));
5427
+ const raw = JSON.parse(readFileSync2(storageConfigPath, "utf-8"));
5428
+ const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
5420
5429
  config.mode = normalizeMode(raw.mode) ?? config.mode;
5421
- config.rds = { ...config.rds, ...raw.rds ?? {} };
5430
+ config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
5422
5431
  } catch {}
5423
5432
  }
5424
5433
  const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
@@ -5435,7 +5444,7 @@ function getStorageConnectionString(dbName = "recordings") {
5435
5444
  if (direct)
5436
5445
  return direct;
5437
5446
  const config = getStorageConfig();
5438
- const { host, port, username, password_env, ssl } = config.rds;
5447
+ const { host, port, username, password_env, ssl } = config.postgres;
5439
5448
  if (!host || !username) {
5440
5449
  throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
5441
5450
  }
@@ -5587,8 +5596,9 @@ async function runStorageMigrations(remote) {
5587
5596
  function getStorageStatus(db = getDatabase()) {
5588
5597
  const config = getStorageConfig();
5589
5598
  const activeEnv = getStorageDatabaseEnv();
5599
+ const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
5590
5600
  return {
5591
- configured: Boolean(activeEnv),
5601
+ configured: Boolean(activeEnv) || hasConfiguredPostgres,
5592
5602
  mode: config.mode,
5593
5603
  enabled: config.mode === "hybrid" || config.mode === "remote",
5594
5604
  env: STORAGE_DATABASE_ENV,
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.35",
4923
+ version: "0.1.36",
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: {
@@ -4954,7 +4954,9 @@ var require_package = __commonJS((exports, module) => {
4954
4954
  "test:coverage": "bun test --coverage",
4955
4955
  "dev:cli": "bun run src/cli/index.ts",
4956
4956
  "dev:mcp": "bun run src/mcp/index.ts",
4957
- prepack: "bun run build",
4957
+ "verify:release": "bun run scripts/release-guard.ts",
4958
+ prepack: "bun run build && bun run verify:release",
4959
+ prepublishOnly: "bun run typecheck && bun run test",
4958
4960
  postinstall: "bash scripts/install_macos_app.sh --postinstall"
4959
4961
  },
4960
4962
  files: [
@@ -9810,17 +9812,18 @@ async function processText(rawText, config, systemPrompt) {
9810
9812
  }
9811
9813
 
9812
9814
  // src/version.ts
9813
- var VERSION = "0.1.35";
9815
+ var VERSION = "0.1.36";
9814
9816
 
9815
9817
  // src/db/storage-config.ts
9816
9818
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
9817
9819
  import { homedir as homedir2 } from "os";
9818
9820
  import { join as join2 } from "path";
9819
- var STORAGE_CONFIG_PATH = join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
9821
+ var LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
9820
9822
  var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
9821
9823
  var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
9822
9824
  var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
9823
9825
  var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
9826
+ var RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
9824
9827
  var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
9825
9828
  function readEnv(name) {
9826
9829
  const value = process.env[name]?.trim();
@@ -9847,10 +9850,16 @@ function getStorageDatabaseUrl() {
9847
9850
  const env = getStorageDatabaseEnv();
9848
9851
  return env ? readEnv(env.name) : undefined;
9849
9852
  }
9853
+ function getStorageConfigPath() {
9854
+ const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
9855
+ if (override)
9856
+ return override;
9857
+ return join2(homedir2(), ".hasna", "recordings", "storage", "config.json");
9858
+ }
9850
9859
  function getStorageConfig() {
9851
9860
  const config = {
9852
9861
  mode: "local",
9853
- rds: {
9862
+ postgres: {
9854
9863
  host: "",
9855
9864
  port: 5432,
9856
9865
  username: "",
@@ -9858,11 +9867,13 @@ function getStorageConfig() {
9858
9867
  ssl: true
9859
9868
  }
9860
9869
  };
9861
- if (existsSync2(STORAGE_CONFIG_PATH)) {
9870
+ const storageConfigPath = getStorageConfigPath();
9871
+ if (existsSync2(storageConfigPath)) {
9862
9872
  try {
9863
- const raw = JSON.parse(readFileSync2(STORAGE_CONFIG_PATH, "utf-8"));
9873
+ const raw = JSON.parse(readFileSync2(storageConfigPath, "utf-8"));
9874
+ const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
9864
9875
  config.mode = normalizeMode(raw.mode) ?? config.mode;
9865
- config.rds = { ...config.rds, ...raw.rds ?? {} };
9876
+ config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
9866
9877
  } catch {}
9867
9878
  }
9868
9879
  const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
@@ -9879,7 +9890,7 @@ function getStorageConnectionString(dbName = "recordings") {
9879
9890
  if (direct)
9880
9891
  return direct;
9881
9892
  const config = getStorageConfig();
9882
- const { host, port, username, password_env, ssl } = config.rds;
9893
+ const { host, port, username, password_env, ssl } = config.postgres;
9883
9894
  if (!host || !username) {
9884
9895
  throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
9885
9896
  }
@@ -10083,8 +10094,9 @@ async function runStorageMigrations(remote) {
10083
10094
  function getStorageStatus(db = getDatabase()) {
10084
10095
  const config = getStorageConfig();
10085
10096
  const activeEnv = getStorageDatabaseEnv();
10097
+ const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
10086
10098
  return {
10087
- configured: Boolean(activeEnv),
10099
+ configured: Boolean(activeEnv) || hasConfiguredPostgres,
10088
10100
  mode: config.mode,
10089
10101
  enabled: config.mode === "hybrid" || config.mode === "remote",
10090
10102
  env: STORAGE_DATABASE_ENV,
package/dist/storage.js CHANGED
@@ -4906,11 +4906,12 @@ var require_lib2 = __commonJS((exports, module) => {
4906
4906
  import { existsSync, readFileSync } from "fs";
4907
4907
  import { homedir } from "os";
4908
4908
  import { join } from "path";
4909
- var STORAGE_CONFIG_PATH = join(homedir(), ".hasna", "recordings", "storage", "config.json");
4909
+ var LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
4910
4910
  var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
4911
4911
  var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
4912
4912
  var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
4913
4913
  var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
4914
+ var RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
4914
4915
  var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
4915
4916
  var STORAGE_MODE_ENV = [RECORDINGS_STORAGE_MODE_ENV, RECORDINGS_STORAGE_MODE_FALLBACK_ENV];
4916
4917
  function readEnv(name) {
@@ -4938,10 +4939,16 @@ function getStorageDatabaseUrl() {
4938
4939
  const env = getStorageDatabaseEnv();
4939
4940
  return env ? readEnv(env.name) : undefined;
4940
4941
  }
4942
+ function getStorageConfigPath() {
4943
+ const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
4944
+ if (override)
4945
+ return override;
4946
+ return join(homedir(), ".hasna", "recordings", "storage", "config.json");
4947
+ }
4941
4948
  function getStorageConfig() {
4942
4949
  const config = {
4943
4950
  mode: "local",
4944
- rds: {
4951
+ postgres: {
4945
4952
  host: "",
4946
4953
  port: 5432,
4947
4954
  username: "",
@@ -4949,11 +4956,13 @@ function getStorageConfig() {
4949
4956
  ssl: true
4950
4957
  }
4951
4958
  };
4952
- if (existsSync(STORAGE_CONFIG_PATH)) {
4959
+ const storageConfigPath = getStorageConfigPath();
4960
+ if (existsSync(storageConfigPath)) {
4953
4961
  try {
4954
- const raw = JSON.parse(readFileSync(STORAGE_CONFIG_PATH, "utf-8"));
4962
+ const raw = JSON.parse(readFileSync(storageConfigPath, "utf-8"));
4963
+ const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
4955
4964
  config.mode = normalizeMode(raw.mode) ?? config.mode;
4956
- config.rds = { ...config.rds, ...raw.rds ?? {} };
4965
+ config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
4957
4966
  } catch {}
4958
4967
  }
4959
4968
  const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
@@ -4970,7 +4979,7 @@ function getStorageConnectionString(dbName = "recordings") {
4970
4979
  if (direct)
4971
4980
  return direct;
4972
4981
  const config = getStorageConfig();
4973
- const { host, port, username, password_env, ssl } = config.rds;
4982
+ const { host, port, username, password_env, ssl } = config.postgres;
4974
4983
  if (!host || !username) {
4975
4984
  throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
4976
4985
  }
@@ -5561,8 +5570,9 @@ async function runStorageMigrations(remote) {
5561
5570
  function getStorageStatus(db = getDatabase()) {
5562
5571
  const config = getStorageConfig();
5563
5572
  const activeEnv = getStorageDatabaseEnv();
5573
+ const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
5564
5574
  return {
5565
- configured: Boolean(activeEnv),
5575
+ configured: Boolean(activeEnv) || hasConfiguredPostgres,
5566
5576
  mode: config.mode,
5567
5577
  enabled: config.mode === "hybrid" || config.mode === "remote",
5568
5578
  env: STORAGE_DATABASE_ENV,
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.35";
1
+ export declare const VERSION = "0.1.36";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/recordings",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "type": "module",
5
5
  "description": "Speech-to-text recording tool with MCP and CLI — records, transcribes, and optionally enhances text using AI",
6
6
  "repository": {
@@ -34,7 +34,9 @@
34
34
  "test:coverage": "bun test --coverage",
35
35
  "dev:cli": "bun run src/cli/index.ts",
36
36
  "dev:mcp": "bun run src/mcp/index.ts",
37
- "prepack": "bun run build",
37
+ "verify:release": "bun run scripts/release-guard.ts",
38
+ "prepack": "bun run build && bun run verify:release",
39
+ "prepublishOnly": "bun run typecheck && bun run test",
38
40
  "postinstall": "bash scripts/install_macos_app.sh --postinstall"
39
41
  },
40
42
  "files": [
@@ -35,12 +35,12 @@ NATIVE_DIR="${PACKAGE_ROOT}/src/native/Recordings"
35
35
  APP_SOURCE="${NATIVE_DIR}/.build/${MODE}/Recordings.app"
36
36
  APP_DEST="${DATA_DIR}/Recordings.app"
37
37
 
38
- mkdir -p "${DATA_DIR}/audio"
39
-
40
38
  if [ "$(uname -s)" != "Darwin" ]; then
41
39
  exit 0
42
40
  fi
43
41
 
42
+ mkdir -p "${DATA_DIR}/audio"
43
+
44
44
  warn_or_fail() {
45
45
  local message="$1"
46
46
  if [ "$POSTINSTALL" -eq 1 ]; then
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { join, relative } from "node:path";
4
+
5
+ type Finding = {
6
+ file: string;
7
+ marker: string;
8
+ kind: "retired-cloud" | "secret-pattern";
9
+ };
10
+
11
+ type PatternCheck = {
12
+ label: string;
13
+ pattern: RegExp;
14
+ };
15
+
16
+ const repoRoot = process.cwd();
17
+
18
+ const roots = [
19
+ "package.json",
20
+ "README.md",
21
+ "scripts",
22
+ "src/storage.ts",
23
+ "src/index.ts",
24
+ "src/cli",
25
+ "src/db",
26
+ "src/lib",
27
+ "src/mcp",
28
+ "src/types",
29
+ "src/native/Recordings/App",
30
+ "src/native/Recordings/RecordingsLib",
31
+ "src/native/Recordings/RecordingsTests",
32
+ "src/native/Recordings/Package.swift",
33
+ "src/native/Recordings/Package.resolved",
34
+ "src/native/Recordings/build.sh",
35
+ "dist",
36
+ ];
37
+
38
+ const ignoredDirs = new Set([".git", "node_modules", ".build"]);
39
+
40
+ const retiredCloudMarkers = [
41
+ ["@hasna", "cloud"].join("/"),
42
+ ["open", "cloud"].join("-"),
43
+ ["cloud", "mcp"].join("-"),
44
+ "register" + "CloudTools",
45
+ "register" + "CloudCommands",
46
+ ["HASNA", "CLOUD"].join("_"),
47
+ ["OPEN", "CLOUD"].join("_"),
48
+ [".hasna", "cloud"].join("/"),
49
+ "--" + "cloud",
50
+ ["cloud", "setup"].join(" "),
51
+ ["cloud", "sync"].join(" "),
52
+ ["Cloud", "Sync"].join(" "),
53
+ ["HASNA", ["R", "D", "S"].join(""), "PASSWORD"].join("_"),
54
+ ];
55
+
56
+ const secretPatterns: PatternCheck[] = [
57
+ { label: ["sk", "ant", ""].join("-"), pattern: new RegExp(["sk", "ant", ""].join("-")) },
58
+ { label: ["sk", "proj", ""].join("-"), pattern: new RegExp(["sk", "proj", ""].join("-")) },
59
+ { label: ["npm", ""].join("_"), pattern: new RegExp(["npm", ""].join("_") + "[A-Za-z]") },
60
+ { label: ["gho", ""].join("_"), pattern: new RegExp(["gho", ""].join("_")) },
61
+ { label: ["ghp", ""].join("_"), pattern: new RegExp(["ghp", ""].join("_")) },
62
+ { label: ["secret", "token"].join("-") + ":", pattern: new RegExp(["secret", "token"].join("-") + ":") },
63
+ { label: "ctx7sk" + "-", pattern: new RegExp("ctx7sk" + "-") },
64
+ { label: ["xai", ""].join("-"), pattern: new RegExp(["xai", ""].join("-")) },
65
+ { label: "AI" + "za" + "[A-Za-z0-9]", pattern: new RegExp("AI" + "za" + "[A-Za-z0-9]") },
66
+ { label: "AK" + "IA" + "[A-Z0-9]", pattern: new RegExp("AK" + "IA" + "[A-Z0-9]") },
67
+ ];
68
+
69
+ const legacyDatabasePatterns: PatternCheck[] = [
70
+ {
71
+ label: ["legacy", "database", ["r", "d", "s"].join("-")].join("-"),
72
+ pattern: new RegExp("\\b" + ["r", "d", "s"].join("") + "\\b", "i"),
73
+ },
74
+ ];
75
+
76
+ function isText(buffer: Buffer): boolean {
77
+ return !buffer.includes(0);
78
+ }
79
+
80
+ function collectFiles(path: string): string[] {
81
+ if (!existsSync(path)) return [];
82
+ const stat = statSync(path);
83
+ if (stat.isFile()) return [path];
84
+ if (!stat.isDirectory()) return [];
85
+
86
+ const files: string[] = [];
87
+ for (const entry of readdirSync(path, { withFileTypes: true })) {
88
+ if (entry.isDirectory() && ignoredDirs.has(entry.name)) continue;
89
+ files.push(...collectFiles(join(path, entry.name)));
90
+ }
91
+ return files;
92
+ }
93
+
94
+ const findings: Finding[] = [];
95
+ for (const file of roots.flatMap((root) => collectFiles(join(repoRoot, root)))) {
96
+ const buffer = readFileSync(file);
97
+ if (!isText(buffer)) continue;
98
+
99
+ const content = buffer.toString("utf8");
100
+ const relativeFile = relative(repoRoot, file);
101
+
102
+ for (const marker of retiredCloudMarkers) {
103
+ if (content.includes(marker)) {
104
+ findings.push({ file: relativeFile, marker, kind: "retired-cloud" });
105
+ }
106
+ }
107
+
108
+ for (const check of legacyDatabasePatterns) {
109
+ if (check.pattern.test(content)) {
110
+ findings.push({ file: relativeFile, marker: check.label, kind: "retired-cloud" });
111
+ }
112
+ }
113
+
114
+ for (const check of secretPatterns) {
115
+ if (check.pattern.test(content)) {
116
+ findings.push({ file: relativeFile, marker: check.label, kind: "secret-pattern" });
117
+ }
118
+ }
119
+ }
120
+
121
+ if (findings.length > 0) {
122
+ console.error("Release guard failed:");
123
+ for (const finding of findings) {
124
+ console.error(` ${finding.kind}: ${finding.file}: ${finding.marker}`);
125
+ }
126
+ process.exit(1);
127
+ }
128
+
129
+ console.log("Release guard passed: package-visible files are free of retired cloud and secret markers.");
@@ -19,7 +19,7 @@ struct CLIRunnerTests {
19
19
 
20
20
  @Test("parseError maps invalid API key (401) to a friendly message")
21
21
  func invalidKeyError() {
22
- let input = "ERROR: Transcription failed: 401 Incorrect API key provided: sk-proj-****vosA. You can find your API key at https://platform.openai.com."
22
+ let input = "ERROR: Transcription failed: 401 Incorrect API key provided: " + "sk" + "-proj-" + "****vosA. You can find your API key at https://platform.openai.com."
23
23
  #expect(CLIRunner.parseError(input) == "OpenAI API key invalid or expired — update it in Recordings Settings")
24
24
  }
25
25