@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 +17 -0
- package/dist/cli/index.js +22 -10
- package/dist/db/storage-config.d.ts +2 -1
- package/dist/db/storage-config.d.ts.map +1 -1
- package/dist/db/storage-sync.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/mcp/index.js +22 -10
- package/dist/storage.js +17 -7
- package/dist/version.d.ts +1 -1
- package/package.json +4 -2
- package/scripts/install_macos_app.sh +2 -2
- package/scripts/release-guard.ts +129 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1 -1
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7052
|
+
const storageConfigPath = getStorageConfigPath();
|
|
7053
|
+
if (existsSync4(storageConfigPath)) {
|
|
7044
7054
|
try {
|
|
7045
|
-
const raw = JSON.parse(readFileSync2(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5424
|
+
const storageConfigPath = getStorageConfigPath();
|
|
5425
|
+
if (existsSync2(storageConfigPath)) {
|
|
5418
5426
|
try {
|
|
5419
|
-
const raw = JSON.parse(readFileSync2(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9870
|
+
const storageConfigPath = getStorageConfigPath();
|
|
9871
|
+
if (existsSync2(storageConfigPath)) {
|
|
9862
9872
|
try {
|
|
9863
|
-
const raw = JSON.parse(readFileSync2(
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4959
|
+
const storageConfigPath = getStorageConfigPath();
|
|
4960
|
+
if (existsSync(storageConfigPath)) {
|
|
4953
4961
|
try {
|
|
4954
|
-
const raw = JSON.parse(readFileSync(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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
|
|