@hasna/recordings 0.1.35 → 0.1.37
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 +60 -0
- package/dist/cli/index.js +34 -13
- 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.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -7
- package/dist/mcp/index.js +34 -13
- package/dist/sdk/index.d.ts +16 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +171 -0
- package/dist/sdk/v1.generated.d.ts +150 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +49 -0
- package/dist/server/cloud.d.ts.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +6812 -0
- package/dist/server/openapi.d.ts +614 -0
- package/dist/server/openapi.d.ts.map +1 -0
- package/dist/server/repo.d.ts +34 -0
- package/dist/server/repo.d.ts.map +1 -0
- package/dist/server/serve.d.ts +11 -0
- package/dist/server/serve.d.ts.map +1 -0
- package/dist/server/v1.d.ts +6 -0
- package/dist/server/v1.d.ts.map +1 -0
- package/dist/storage.js +17 -7
- package/dist/version.d.ts +1 -1
- package/package.json +16 -5
- package/scripts/generate-sdk.ts +31 -0
- package/scripts/install_macos_app.sh +2 -2
- package/scripts/migrate.ts +37 -0
- package/scripts/release-guard.ts +129 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1 -1
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.37";
|
|
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.37",
|
|
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": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
12
|
"bin": {
|
|
13
13
|
"recordings": "dist/cli/index.js",
|
|
14
|
-
"recordings-mcp": "dist/mcp/index.js"
|
|
14
|
+
"recordings-mcp": "dist/mcp/index.js",
|
|
15
|
+
"recordings-serve": "dist/server/index.js"
|
|
15
16
|
},
|
|
16
17
|
"exports": {
|
|
17
18
|
".": {
|
|
@@ -21,20 +22,29 @@
|
|
|
21
22
|
"./storage": {
|
|
22
23
|
"import": "./dist/storage.js",
|
|
23
24
|
"types": "./dist/storage.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./sdk": {
|
|
27
|
+
"import": "./dist/sdk/index.js",
|
|
28
|
+
"types": "./dist/sdk/index.d.ts"
|
|
24
29
|
}
|
|
25
30
|
},
|
|
26
31
|
"scripts": {
|
|
27
32
|
"clean": "rm -rf dist",
|
|
28
|
-
"build": "bun run clean && bun run build:cli && bun run build:mcp && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
|
|
33
|
+
"build": "bun run clean && bun run build:cli && bun run build:mcp && bun run build:serve && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
|
|
29
34
|
"build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
|
|
30
35
|
"build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
|
|
31
|
-
"build:
|
|
36
|
+
"build:serve": "bun build src/server/index.ts --target=bun --outfile=dist/server/index.js --external=@modelcontextprotocol/sdk --external=@hasna/contracts --external=openai --external=pg",
|
|
37
|
+
"build:lib": "bun build src/index.ts src/storage.ts src/sdk/index.ts --target=bun --outdir=dist --external=openai",
|
|
38
|
+
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
39
|
+
"migrate": "bun run scripts/migrate.ts",
|
|
32
40
|
"typecheck": "tsc --noEmit",
|
|
33
41
|
"test": "bun test",
|
|
34
42
|
"test:coverage": "bun test --coverage",
|
|
35
43
|
"dev:cli": "bun run src/cli/index.ts",
|
|
36
44
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
37
|
-
"
|
|
45
|
+
"verify:release": "bun run scripts/release-guard.ts",
|
|
46
|
+
"prepack": "bun run build && bun run verify:release",
|
|
47
|
+
"prepublishOnly": "bun run typecheck && bun run test",
|
|
38
48
|
"postinstall": "bash scripts/install_macos_app.sh --postinstall"
|
|
39
49
|
},
|
|
40
50
|
"files": [
|
|
@@ -50,6 +60,7 @@
|
|
|
50
60
|
"LICENSE"
|
|
51
61
|
],
|
|
52
62
|
"dependencies": {
|
|
63
|
+
"@hasna/contracts": "0.4.1",
|
|
53
64
|
"@hasna/events": "^0.1.11",
|
|
54
65
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
55
66
|
"chalk": "^5.4.1",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Generate the typed `/v1` SDK client from the serve OpenAPI document.
|
|
4
|
+
*
|
|
5
|
+
* Source of truth: src/server/openapi.ts (also served at GET /openapi.json).
|
|
6
|
+
* Output: src/sdk/v1.generated.ts — a dependency-free typed fetch client.
|
|
7
|
+
*
|
|
8
|
+
* Usage: bun run scripts/generate-sdk.ts
|
|
9
|
+
*/
|
|
10
|
+
import { writeFileSync } from "node:fs";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { generateSdkFromOpenApi } from "@hasna/contracts/sdk";
|
|
14
|
+
import { buildV1OpenApiDocument } from "../src/server/openapi.js";
|
|
15
|
+
|
|
16
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
const spec = buildV1OpenApiDocument();
|
|
18
|
+
const { code, operations, warnings } = generateSdkFromOpenApi(spec as never, {
|
|
19
|
+
className: "RecordingsV1Client",
|
|
20
|
+
apiKeyHeader: "x-api-key",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const header =
|
|
24
|
+
"// AUTO-GENERATED by scripts/generate-sdk.ts from src/server/openapi.ts — DO NOT EDIT BY HAND.\n" +
|
|
25
|
+
"// Regenerate: bun run scripts/generate-sdk.ts\n\n";
|
|
26
|
+
const outPath = join(root, "src/sdk/v1.generated.ts");
|
|
27
|
+
writeFileSync(outPath, header + code);
|
|
28
|
+
|
|
29
|
+
console.log(`Wrote ${outPath}`);
|
|
30
|
+
console.log(`Operations: ${operations.map((o) => o.functionName).join(", ")}`);
|
|
31
|
+
if (warnings.length) console.log(`Warnings:\n ${warnings.join("\n ")}`);
|
|
@@ -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,37 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Migration runner for the recordings cloud (A1 pure-remote) database.
|
|
4
|
+
*
|
|
5
|
+
* Applies the relational recordings schema and the contracts API-key store,
|
|
6
|
+
* idempotently (CREATE ... IF NOT EXISTS). NEVER drops or rewrites existing
|
|
7
|
+
* tables — safe to run against a populated DB. Shares the exact code path used
|
|
8
|
+
* by the serve process (`ensureCloudSchema`).
|
|
9
|
+
*
|
|
10
|
+
* The canonical DDL is committed under migrations/*.sql for transparency.
|
|
11
|
+
*
|
|
12
|
+
* Env: HASNA_RECORDINGS_DATABASE_URL (or RECORDINGS_DATABASE_URL / DATABASE_URL).
|
|
13
|
+
* Usage: bun run scripts/migrate.ts
|
|
14
|
+
*/
|
|
15
|
+
import { ensureCloudSchema, pingCloud, resolveCloudDatabaseUrl, closeCloud } from "../src/server/cloud.js";
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const url = resolveCloudDatabaseUrl();
|
|
19
|
+
if (!url) {
|
|
20
|
+
console.error(
|
|
21
|
+
"migrate: no database URL (HASNA_RECORDINGS_DATABASE_URL / RECORDINGS_DATABASE_URL / DATABASE_URL)",
|
|
22
|
+
);
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
console.log("migrate: connecting…");
|
|
26
|
+
await pingCloud();
|
|
27
|
+
console.log("migrate: applying schema (recordings tables + api_keys)…");
|
|
28
|
+
await ensureCloudSchema();
|
|
29
|
+
console.log("migrate: done");
|
|
30
|
+
await closeCloud();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch((e) => {
|
|
35
|
+
console.error("migrate: failed:", (e as Error).message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
@@ -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
|
|