@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/README.md +17 -0
- package/dist/cli/index.js +328 -95
- 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 +79 -57
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/mcp/index.js +85 -61
- package/dist/storage.js +79 -57
- package/dist/version.d.ts +1 -1
- package/package.json +5 -3
- 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/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((
|
|
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((
|
|
3622
|
+
return new this._Promise((resolve2, reject) => {
|
|
3623
3623
|
this._connect((error) => {
|
|
3624
3624
|
if (error) {
|
|
3625
3625
|
reject(error);
|
|
3626
3626
|
} else {
|
|
3627
|
-
|
|
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((
|
|
3960
|
-
query.callback = (err, res) => err ? reject(err) :
|
|
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((
|
|
4036
|
-
this.connection.once("end",
|
|
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(
|
|
4084
|
-
res =
|
|
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((
|
|
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(
|
|
4522
|
-
this._once("end",
|
|
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((
|
|
4696
|
+
return new this._Promise((resolve2, reject) => {
|
|
4697
4697
|
this._connect((error) => {
|
|
4698
4698
|
if (error) {
|
|
4699
4699
|
reject(error);
|
|
4700
4700
|
} else {
|
|
4701
|
-
|
|
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((
|
|
4726
|
-
resolveOut =
|
|
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(
|
|
4785
|
-
cb = (err) => err ? reject(err) :
|
|
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
|
|
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
|
}
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
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 (
|
|
5134
|
+
if (existsSync2(oldDir)) {
|
|
5145
5135
|
try {
|
|
5146
|
-
|
|
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(
|
|
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 =
|
|
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.
|
|
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": [
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
"LICENSE"
|
|
51
53
|
],
|
|
52
54
|
"dependencies": {
|
|
53
|
-
"@hasna/events": "^0.1.
|
|
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
|
|
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
|
|