@hasna/recordings 0.1.34 → 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/dist/storage.js CHANGED
@@ -2940,7 +2940,7 @@ var require_dist = __commonJS((exports) => {
2940
2940
  function parse(stream, callback) {
2941
2941
  const parser = new parser_1.Parser;
2942
2942
  stream.on("data", (buffer) => parser.parse(buffer, callback));
2943
- return new Promise((resolve) => stream.on("end", () => resolve()));
2943
+ return new Promise((resolve2) => stream.on("end", () => resolve2()));
2944
2944
  }
2945
2945
  exports.parse = parse;
2946
2946
  });
@@ -3619,12 +3619,12 @@ var require_client = __commonJS((exports, module) => {
3619
3619
  this._connect(callback);
3620
3620
  return;
3621
3621
  }
3622
- return new this._Promise((resolve, reject) => {
3622
+ return new this._Promise((resolve2, reject) => {
3623
3623
  this._connect((error) => {
3624
3624
  if (error) {
3625
3625
  reject(error);
3626
3626
  } else {
3627
- resolve(this);
3627
+ resolve2(this);
3628
3628
  }
3629
3629
  });
3630
3630
  });
@@ -3956,8 +3956,8 @@ var require_client = __commonJS((exports, module) => {
3956
3956
  readTimeout = config.query_timeout || this.connectionParameters.query_timeout;
3957
3957
  query = new Query(config, values, callback);
3958
3958
  if (!query.callback) {
3959
- result = new this._Promise((resolve, reject) => {
3960
- query.callback = (err, res) => err ? reject(err) : resolve(res);
3959
+ result = new this._Promise((resolve2, reject) => {
3960
+ query.callback = (err, res) => err ? reject(err) : resolve2(res);
3961
3961
  }).catch((err) => {
3962
3962
  Error.captureStackTrace(err);
3963
3963
  throw err;
@@ -4032,8 +4032,8 @@ var require_client = __commonJS((exports, module) => {
4032
4032
  if (cb) {
4033
4033
  this.connection.once("end", cb);
4034
4034
  } else {
4035
- return new this._Promise((resolve) => {
4036
- this.connection.once("end", resolve);
4035
+ return new this._Promise((resolve2) => {
4036
+ this.connection.once("end", resolve2);
4037
4037
  });
4038
4038
  }
4039
4039
  }
@@ -4080,8 +4080,8 @@ var require_pg_pool = __commonJS((exports, module) => {
4080
4080
  const cb = function(err, client) {
4081
4081
  err ? rej(err) : res(client);
4082
4082
  };
4083
- const result = new Promise2(function(resolve, reject) {
4084
- res = resolve;
4083
+ const result = new Promise2(function(resolve2, reject) {
4084
+ res = resolve2;
4085
4085
  rej = reject;
4086
4086
  }).catch((err) => {
4087
4087
  Error.captureStackTrace(err);
@@ -4142,7 +4142,7 @@ var require_pg_pool = __commonJS((exports, module) => {
4142
4142
  if (typeof Promise2.try === "function") {
4143
4143
  return Promise2.try(f);
4144
4144
  }
4145
- return new Promise2((resolve) => resolve(f()));
4145
+ return new Promise2((resolve2) => resolve2(f()));
4146
4146
  }
4147
4147
  _isFull() {
4148
4148
  return this._clients.length >= this.options.max;
@@ -4518,8 +4518,8 @@ var require_query2 = __commonJS((exports, module) => {
4518
4518
  NativeQuery.prototype._getPromise = function() {
4519
4519
  if (this._promise)
4520
4520
  return this._promise;
4521
- this._promise = new Promise(function(resolve, reject) {
4522
- this._once("end", resolve);
4521
+ this._promise = new Promise(function(resolve2, reject) {
4522
+ this._once("end", resolve2);
4523
4523
  this._once("error", reject);
4524
4524
  }.bind(this));
4525
4525
  return this._promise;
@@ -4693,12 +4693,12 @@ var require_client2 = __commonJS((exports, module) => {
4693
4693
  this._connect(callback);
4694
4694
  return;
4695
4695
  }
4696
- return new this._Promise((resolve, reject) => {
4696
+ return new this._Promise((resolve2, reject) => {
4697
4697
  this._connect((error) => {
4698
4698
  if (error) {
4699
4699
  reject(error);
4700
4700
  } else {
4701
- resolve(this);
4701
+ resolve2(this);
4702
4702
  }
4703
4703
  });
4704
4704
  });
@@ -4722,8 +4722,8 @@ var require_client2 = __commonJS((exports, module) => {
4722
4722
  query = new NativeQuery(config, values, callback);
4723
4723
  if (!query.callback) {
4724
4724
  let resolveOut, rejectOut;
4725
- result = new this._Promise((resolve, reject) => {
4726
- resolveOut = resolve;
4725
+ result = new this._Promise((resolve2, reject) => {
4726
+ resolveOut = resolve2;
4727
4727
  rejectOut = reject;
4728
4728
  }).catch((err) => {
4729
4729
  Error.captureStackTrace(err);
@@ -4781,8 +4781,8 @@ var require_client2 = __commonJS((exports, module) => {
4781
4781
  }
4782
4782
  let result;
4783
4783
  if (!cb) {
4784
- result = new this._Promise(function(resolve, reject) {
4785
- cb = (err) => err ? reject(err) : resolve();
4784
+ result = new this._Promise(function(resolve2, reject) {
4785
+ cb = (err) => err ? reject(err) : resolve2();
4786
4786
  });
4787
4787
  }
4788
4788
  this.native.end(function() {
@@ -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
  }
@@ -5015,11 +5024,11 @@ class SqliteAdapter {
5015
5024
 
5016
5025
  // src/db/database.ts
5017
5026
  import { mkdirSync as mkdirSync2 } from "fs";
5018
- import { dirname } from "path";
5027
+ import { dirname as dirname2 } from "path";
5019
5028
 
5020
5029
  // src/lib/config.ts
5021
- import { existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync, cpSync, readdirSync, statSync } from "fs";
5022
- import { join as join2 } from "path";
5030
+ import { copyFileSync, existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync, readdirSync, statSync } from "fs";
5031
+ import { dirname, join as join2, resolve } from "path";
5023
5032
  import { homedir as homedir2 } from "os";
5024
5033
  var DEFAULT_CONFIG = {
5025
5034
  openai_api_key: "",
@@ -5113,44 +5122,53 @@ function expandEnvBackedConfig(config) {
5113
5122
  return expanded;
5114
5123
  }
5115
5124
  function findConfigFile() {
5116
- let dir = process.cwd();
5117
- const root = "/";
5118
- while (dir !== root) {
5119
- const candidate = join2(dir, ".recordings", "config.json");
5120
- if (existsSync2(candidate))
5121
- return candidate;
5122
- const parent = join2(dir, "..");
5123
- if (parent === dir)
5124
- break;
5125
- dir = parent;
5126
- }
5127
- return null;
5125
+ return findProjectRecordingsPath("config.json");
5128
5126
  }
5129
5127
  function getDataDir() {
5130
- let dir = process.cwd();
5131
- const root = "/";
5132
- while (dir !== root) {
5133
- const candidate = join2(dir, ".recordings");
5134
- if (existsSync2(candidate))
5135
- return candidate;
5136
- const parent = join2(dir, "..");
5137
- if (parent === dir)
5138
- break;
5139
- dir = parent;
5140
- }
5141
- const home = homedir2();
5128
+ const projectLocalDir = findProjectRecordingsPath();
5129
+ if (projectLocalDir)
5130
+ return projectLocalDir;
5131
+ const home = getHomeDir();
5142
5132
  const newDir = join2(home, ".hasna", "recordings");
5143
5133
  const oldDir = join2(home, ".recordings");
5144
- if (!existsSync2(newDir) && existsSync2(oldDir)) {
5134
+ if (existsSync2(oldDir)) {
5145
5135
  try {
5146
- mkdirSync(join2(home, ".hasna"), { recursive: true });
5147
- cpSync(oldDir, newDir, { recursive: true });
5136
+ mergeDirectoryContents(oldDir, newDir);
5148
5137
  } catch {}
5149
5138
  }
5150
5139
  return newDir;
5151
5140
  }
5141
+ function findProjectRecordingsPath(entry) {
5142
+ const home = resolve(getHomeDir());
5143
+ let dir = resolve(process.cwd());
5144
+ while (true) {
5145
+ if (dir !== home) {
5146
+ const candidate = entry ? join2(dir, ".recordings", entry) : join2(dir, ".recordings");
5147
+ if (existsSync2(candidate))
5148
+ return candidate;
5149
+ }
5150
+ const parent = dirname(dir);
5151
+ if (parent === dir)
5152
+ break;
5153
+ dir = parent;
5154
+ }
5155
+ return null;
5156
+ }
5157
+ function mergeDirectoryContents(sourceDir, targetDir) {
5158
+ mkdirSync(targetDir, { recursive: true });
5159
+ for (const entry of readdirSync(sourceDir)) {
5160
+ const sourcePath = join2(sourceDir, entry);
5161
+ const targetPath = join2(targetDir, entry);
5162
+ const sourceStats = statSync(sourcePath);
5163
+ if (sourceStats.isDirectory()) {
5164
+ mergeDirectoryContents(sourcePath, targetPath);
5165
+ } else if (!existsSync2(targetPath)) {
5166
+ copyFileSync(sourcePath, targetPath);
5167
+ }
5168
+ }
5169
+ }
5152
5170
  function loadSecretKey(keyName) {
5153
- const secretsPath = join2(homedir2(), ".secrets");
5171
+ const secretsPath = join2(getHomeDir(), ".secrets");
5154
5172
  if (!existsSync2(secretsPath))
5155
5173
  return "";
5156
5174
  for (const candidate of listSecretFiles(secretsPath)) {
@@ -5191,6 +5209,9 @@ function listSecretFiles(path) {
5191
5209
  return [];
5192
5210
  }
5193
5211
  }
5212
+ function getHomeDir() {
5213
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
5214
+ }
5194
5215
  function ensureDataDir(config) {
5195
5216
  const { mkdirSync: mkdirSync2 } = __require("fs");
5196
5217
  mkdirSync2(config.audio_dir, { recursive: true });
@@ -5285,7 +5306,7 @@ function getDatabase(dbPath) {
5285
5306
  if (_db)
5286
5307
  return _db;
5287
5308
  const path = dbPath || loadConfig().db_path;
5288
- const dir = dirname(path);
5309
+ const dir = dirname2(path);
5289
5310
  mkdirSync2(dir, { recursive: true });
5290
5311
  _adapter = new SqliteAdapter(path);
5291
5312
  _db = _adapter.raw;
@@ -5549,8 +5570,9 @@ async function runStorageMigrations(remote) {
5549
5570
  function getStorageStatus(db = getDatabase()) {
5550
5571
  const config = getStorageConfig();
5551
5572
  const activeEnv = getStorageDatabaseEnv();
5573
+ const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
5552
5574
  return {
5553
- configured: Boolean(activeEnv),
5575
+ configured: Boolean(activeEnv) || hasConfiguredPostgres,
5554
5576
  mode: config.mode,
5555
5577
  enabled: config.mode === "hybrid" || config.mode === "remote",
5556
5578
  env: STORAGE_DATABASE_ENV,
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.33";
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.34",
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": [
@@ -50,7 +52,7 @@
50
52
  "LICENSE"
51
53
  ],
52
54
  "dependencies": {
53
- "@hasna/events": "^0.1.7",
55
+ "@hasna/events": "^0.1.11",
54
56
  "@modelcontextprotocol/sdk": "^1.12.1",
55
57
  "chalk": "^5.4.1",
56
58
  "commander": "^13.1.0",
@@ -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