@hasna/recordings 0.1.11 → 0.1.13

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.
Files changed (69) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/index.js +395 -37
  3. package/dist/db/database.d.ts.map +1 -1
  4. package/dist/db/recordings.d.ts.map +1 -1
  5. package/dist/index.js +78 -11
  6. package/dist/lib/config.d.ts.map +1 -1
  7. package/dist/lib/enhancer.d.ts.map +1 -1
  8. package/dist/lib/recorder.d.ts.map +1 -1
  9. package/dist/lib/transcriber.d.ts +8 -2
  10. package/dist/lib/transcriber.d.ts.map +1 -1
  11. package/dist/mcp/index.js +147 -30
  12. package/dist/types/index.d.ts +3 -0
  13. package/dist/types/index.d.ts.map +1 -1
  14. package/dist/version.d.ts +2 -0
  15. package/dist/version.d.ts.map +1 -0
  16. package/package.json +20 -3
  17. package/scripts/install_macos_app.sh +76 -0
  18. package/src/native/Recordings/{Recordings → App}/RecordingsApp.swift +5 -1
  19. package/src/native/Recordings/Package.resolved +21 -3
  20. package/src/native/Recordings/Package.swift +16 -5
  21. package/src/native/Recordings/{Recordings → RecordingsLib}/Info.plist +6 -0
  22. package/src/native/Recordings/{Recordings → RecordingsLib}/MenuBarPopover.swift +55 -11
  23. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +36 -0
  24. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +164 -0
  25. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +113 -0
  26. package/src/native/Recordings/{Recordings → RecordingsLib}/ProjectStore.swift +11 -11
  27. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +383 -0
  28. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +835 -0
  29. package/src/native/Recordings/{Recordings → RecordingsLib}/SettingsView.swift +19 -5
  30. package/src/native/Recordings/{Recordings → RecordingsLib}/VoiceShortcuts.swift +12 -8
  31. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +63 -0
  32. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +23 -0
  33. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +33 -0
  34. package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +92 -0
  35. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +58 -0
  36. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +113 -0
  37. package/src/native/Recordings/build.sh +6 -6
  38. package/.takumi/settings.local.json +0 -7
  39. package/bun.lock +0 -250
  40. package/bunfig.toml +0 -2
  41. package/src/__tests__/agents.test.ts +0 -136
  42. package/src/__tests__/config.test.ts +0 -252
  43. package/src/__tests__/database.test.ts +0 -167
  44. package/src/__tests__/enhancer.test.ts +0 -639
  45. package/src/__tests__/preload.ts +0 -4
  46. package/src/__tests__/projects.test.ts +0 -109
  47. package/src/__tests__/recorder.test.ts +0 -278
  48. package/src/__tests__/recordings.test.ts +0 -353
  49. package/src/__tests__/transcriber.test.ts +0 -322
  50. package/src/__tests__/types.test.ts +0 -75
  51. package/src/cli/index.ts +0 -988
  52. package/src/db/agents.ts +0 -104
  53. package/src/db/database.ts +0 -163
  54. package/src/db/pg-migrations.ts +0 -82
  55. package/src/db/projects.ts +0 -71
  56. package/src/db/recordings.ts +0 -225
  57. package/src/index.ts +0 -81
  58. package/src/lib/config.ts +0 -223
  59. package/src/lib/enhancer.ts +0 -173
  60. package/src/lib/recorder.ts +0 -198
  61. package/src/lib/transcriber.ts +0 -105
  62. package/src/mcp/index.ts +0 -464
  63. package/src/native/Recordings/Recordings/RecordingEngine.swift +0 -455
  64. package/src/native/Recordings/test_fn.swift +0 -79
  65. package/src/native/Recordings/test_fn2.swift +0 -33
  66. package/src/types/index.ts +0 -144
  67. package/tsconfig.json +0 -21
  68. /package/src/native/Recordings/{Recordings → RecordingsLib}/FnKeyMonitor.swift +0 -0
  69. /package/src/native/Recordings/{Recordings → RecordingsLib}/Recordings.entitlements +0 -0
@@ -1,252 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
- import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { mkdirSync, rmSync, existsSync, writeFileSync } from "fs";
5
- import { loadConfig, getDataDir, ensureDataDir, DEFAULT_CONFIG } from "../lib/config.js";
6
-
7
- let tempDir: string;
8
-
9
- // Save and restore env vars
10
- const savedEnv: Record<string, string | undefined> = {};
11
- const envKeys = [
12
- "OPENAI_API_KEY",
13
- "RECORDINGS_API_KEY",
14
- "RECORDINGS_ENHANCEMENT_KEY",
15
- "RECORDINGS_MODEL",
16
- "RECORDINGS_ENHANCEMENT_MODEL",
17
- "RECORDINGS_LANGUAGE",
18
- "RECORDINGS_DB_PATH",
19
- "RECORDINGS_AUDIO_DIR",
20
- "RECORDINGS_MAX_SECONDS",
21
- ];
22
-
23
- beforeEach(() => {
24
- tempDir = join(tmpdir(), `open-recordings-test-config-${Date.now()}-${Math.random().toString(36).slice(2)}`);
25
- mkdirSync(tempDir, { recursive: true });
26
-
27
- // Save env vars
28
- for (const key of envKeys) {
29
- savedEnv[key] = process.env[key];
30
- delete process.env[key];
31
- }
32
- });
33
-
34
- afterEach(() => {
35
- // Restore env vars
36
- for (const key of envKeys) {
37
- if (savedEnv[key] !== undefined) {
38
- process.env[key] = savedEnv[key];
39
- } else {
40
- delete process.env[key];
41
- }
42
- }
43
-
44
- if (existsSync(tempDir)) {
45
- rmSync(tempDir, { recursive: true, force: true });
46
- }
47
- });
48
-
49
- describe("DEFAULT_CONFIG", () => {
50
- test("has expected default values", () => {
51
- expect(DEFAULT_CONFIG.transcription_model).toBe("gpt-4o-mini-transcribe");
52
- expect(DEFAULT_CONFIG.enhancement_model).toBe("gpt-4o");
53
- expect(DEFAULT_CONFIG.language).toBe("en");
54
- expect(DEFAULT_CONFIG.audio_format).toBe("wav");
55
- expect(DEFAULT_CONFIG.sample_rate).toBe(16000);
56
- expect(DEFAULT_CONFIG.record_command).toBe("sox");
57
- expect(DEFAULT_CONFIG.hotkey).toBe("space");
58
- expect(DEFAULT_CONFIG.auto_enhance).toBe(true);
59
- expect(DEFAULT_CONFIG.max_recording_seconds).toBe(300);
60
- expect(DEFAULT_CONFIG.enhance_triggers).toContain("say it better");
61
- expect(DEFAULT_CONFIG.enhance_triggers).toContain("rewrite this");
62
- });
63
- });
64
-
65
- describe("loadConfig", () => {
66
- test("returns defaults when no config file or env vars", () => {
67
- const config = loadConfig(join(tempDir, "nonexistent.json"));
68
- expect(config.transcription_model).toBe("gpt-4o-mini-transcribe");
69
- expect(config.enhancement_model).toBe("gpt-4o");
70
- expect(config.language).toBe("en");
71
- expect(config.audio_format).toBe("wav");
72
- expect(config.auto_enhance).toBe(true);
73
- });
74
-
75
- test("loads config from file", () => {
76
- const configPath = join(tempDir, "config.json");
77
- writeFileSync(
78
- configPath,
79
- JSON.stringify({
80
- transcription_model: "whisper-1",
81
- language: "fr",
82
- auto_enhance: false,
83
- })
84
- );
85
-
86
- const config = loadConfig(configPath);
87
- expect(config.transcription_model).toBe("whisper-1");
88
- expect(config.language).toBe("fr");
89
- expect(config.auto_enhance).toBe(false);
90
- // Other defaults still present
91
- expect(config.audio_format).toBe("wav");
92
- });
93
-
94
- test("ignores invalid JSON config file", () => {
95
- const configPath = join(tempDir, "bad-config.json");
96
- writeFileSync(configPath, "this is not json {{{");
97
-
98
- const config = loadConfig(configPath);
99
- // Should fall back to defaults
100
- expect(config.transcription_model).toBe("gpt-4o-mini-transcribe");
101
- });
102
-
103
- test("env var OPENAI_API_KEY overrides config", () => {
104
- process.env.OPENAI_API_KEY = "sk-env-key";
105
- const config = loadConfig(join(tempDir, "nonexistent.json"));
106
- expect(config.openai_api_key).toBe("sk-env-key");
107
- });
108
-
109
- test("env var RECORDINGS_API_KEY overrides OPENAI_API_KEY", () => {
110
- process.env.OPENAI_API_KEY = "sk-openai";
111
- process.env.RECORDINGS_API_KEY = "sk-recordings";
112
- const config = loadConfig(join(tempDir, "nonexistent.json"));
113
- expect(config.openai_api_key).toBe("sk-recordings");
114
- });
115
-
116
- test("env var RECORDINGS_ENHANCEMENT_KEY sets enhancement_api_key", () => {
117
- process.env.RECORDINGS_ENHANCEMENT_KEY = "sk-enhance";
118
- const config = loadConfig(join(tempDir, "nonexistent.json"));
119
- expect(config.enhancement_api_key).toBe("sk-enhance");
120
- });
121
-
122
- test("env var RECORDINGS_MODEL overrides transcription_model", () => {
123
- process.env.RECORDINGS_MODEL = "whisper-1";
124
- const config = loadConfig(join(tempDir, "nonexistent.json"));
125
- expect(config.transcription_model).toBe("whisper-1");
126
- });
127
-
128
- test("env var RECORDINGS_ENHANCEMENT_MODEL overrides enhancement_model", () => {
129
- process.env.RECORDINGS_ENHANCEMENT_MODEL = "gpt-3.5-turbo";
130
- const config = loadConfig(join(tempDir, "nonexistent.json"));
131
- expect(config.enhancement_model).toBe("gpt-3.5-turbo");
132
- });
133
-
134
- test("env var RECORDINGS_LANGUAGE overrides language", () => {
135
- process.env.RECORDINGS_LANGUAGE = "de";
136
- const config = loadConfig(join(tempDir, "nonexistent.json"));
137
- expect(config.language).toBe("de");
138
- });
139
-
140
- test("env var RECORDINGS_DB_PATH overrides db_path", () => {
141
- process.env.RECORDINGS_DB_PATH = "/custom/db.sqlite";
142
- const config = loadConfig(join(tempDir, "nonexistent.json"));
143
- expect(config.db_path).toBe("/custom/db.sqlite");
144
- });
145
-
146
- test("env var RECORDINGS_AUDIO_DIR overrides audio_dir", () => {
147
- process.env.RECORDINGS_AUDIO_DIR = "/custom/audio";
148
- const config = loadConfig(join(tempDir, "nonexistent.json"));
149
- expect(config.audio_dir).toBe("/custom/audio");
150
- });
151
-
152
- test("env var RECORDINGS_MAX_SECONDS overrides max_recording_seconds", () => {
153
- process.env.RECORDINGS_MAX_SECONDS = "60";
154
- const config = loadConfig(join(tempDir, "nonexistent.json"));
155
- expect(config.max_recording_seconds).toBe(60);
156
- });
157
-
158
- test("sets default db_path when not configured", () => {
159
- const config = loadConfig(join(tempDir, "nonexistent.json"));
160
- expect(config.db_path).toBeTruthy();
161
- expect(config.db_path).toContain("recordings.db");
162
- });
163
-
164
- test("sets default audio_dir when not configured", () => {
165
- const config = loadConfig(join(tempDir, "nonexistent.json"));
166
- expect(config.audio_dir).toBeTruthy();
167
- expect(config.audio_dir).toContain("audio");
168
- });
169
-
170
- test("enhancement_api_key falls back to openai_api_key", () => {
171
- process.env.OPENAI_API_KEY = "sk-shared";
172
- const config = loadConfig(join(tempDir, "nonexistent.json"));
173
- expect(config.enhancement_api_key).toBe("sk-shared");
174
- });
175
-
176
- test("file config values are overridden by env vars", () => {
177
- const configPath = join(tempDir, "config.json");
178
- writeFileSync(
179
- configPath,
180
- JSON.stringify({ language: "fr", transcription_model: "file-model" })
181
- );
182
- process.env.RECORDINGS_LANGUAGE = "ja";
183
-
184
- const config = loadConfig(configPath);
185
- expect(config.language).toBe("ja"); // env wins
186
- expect(config.transcription_model).toBe("file-model"); // no env override for this
187
- });
188
- });
189
-
190
- describe("ensureDataDir", () => {
191
- test("creates audio_dir and db directory", () => {
192
- const audioDir = join(tempDir, "audio-out");
193
- const dbPath = join(tempDir, "db-dir/recordings.db");
194
- const config = {
195
- ...DEFAULT_CONFIG,
196
- audio_dir: audioDir,
197
- db_path: dbPath,
198
- };
199
-
200
- ensureDataDir(config);
201
- expect(existsSync(audioDir)).toBe(true);
202
- expect(existsSync(join(tempDir, "db-dir"))).toBe(true);
203
- });
204
- });
205
-
206
- describe("getDataDir", () => {
207
- test("returns a string", () => {
208
- const dir = getDataDir();
209
- expect(typeof dir).toBe("string");
210
- expect(dir).toContain("recordings");
211
- });
212
- });
213
-
214
- describe("loadSecretKey (via loadConfig)", () => {
215
- test("loads API key from ~/.secrets with double quotes", () => {
216
- const config = loadConfig(join(tempDir, "nonexistent.json"));
217
- // The key is either loaded from ~/.secrets or empty
218
- expect(typeof config.openai_api_key).toBe("string");
219
- });
220
-
221
- test("loads secret key with single quotes format", () => {
222
- // We can't easily mock ~/.secrets, but we test the regex patterns directly
223
- // by testing the config loading with different env overrides
224
- // The loadSecretKey function is internal, so we test its effect indirectly
225
- // When no env var is set and no ~/.secrets has the key, it returns ""
226
- const config = loadConfig(join(tempDir, "nonexistent.json"));
227
- // At minimum, the function doesn't crash
228
- expect(config).toBeDefined();
229
- });
230
- });
231
-
232
- describe("findConfigFile (via loadConfig without explicit path)", () => {
233
- test("loads config without explicit path (uses findConfigFile)", () => {
234
- // When no configPath is given, findConfigFile walks up from cwd
235
- const config = loadConfig();
236
- expect(config).toBeDefined();
237
- expect(config.transcription_model).toBeTruthy();
238
- });
239
- });
240
-
241
- describe("ensureDataDir edge cases", () => {
242
- test("handles db_path without directory separator", () => {
243
- const config = {
244
- ...DEFAULT_CONFIG,
245
- audio_dir: join(tempDir, "audio"),
246
- db_path: "recordings.db", // no directory part
247
- };
248
- // Should not throw - dbDir will be empty string
249
- ensureDataDir(config);
250
- expect(existsSync(join(tempDir, "audio"))).toBe(true);
251
- });
252
- });
@@ -1,167 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
- import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { mkdirSync, rmSync, existsSync } from "fs";
5
- import {
6
- getDatabase,
7
- closeDatabase,
8
- resetDatabase,
9
- shortUuid,
10
- } from "../db/database.js";
11
-
12
- let tempDir: string;
13
-
14
- beforeEach(() => {
15
- resetDatabase();
16
- tempDir = join(tmpdir(), `open-recordings-test-db-${Date.now()}-${Math.random().toString(36).slice(2)}`);
17
- mkdirSync(tempDir, { recursive: true });
18
- });
19
-
20
- afterEach(() => {
21
- closeDatabase();
22
- resetDatabase();
23
- if (existsSync(tempDir)) {
24
- rmSync(tempDir, { recursive: true, force: true });
25
- }
26
- });
27
-
28
- describe("getDatabase", () => {
29
- test("creates database file and returns a database instance", () => {
30
- const dbPath = join(tempDir, "test.db");
31
- const db = getDatabase(dbPath);
32
- expect(db).toBeDefined();
33
- expect(existsSync(dbPath)).toBe(true);
34
- });
35
-
36
- test("creates parent directories if they don't exist", () => {
37
- const dbPath = join(tempDir, "nested", "deep", "test.db");
38
- const db = getDatabase(dbPath);
39
- expect(db).toBeDefined();
40
- expect(existsSync(dbPath)).toBe(true);
41
- });
42
-
43
- test("returns the same instance on subsequent calls (singleton)", () => {
44
- const dbPath = join(tempDir, "test.db");
45
- const db1 = getDatabase(dbPath);
46
- const db2 = getDatabase(dbPath);
47
- expect(db1).toBe(db2);
48
- });
49
-
50
- test("runs migrations on creation", () => {
51
- const dbPath = join(tempDir, "test.db");
52
- const db = getDatabase(dbPath);
53
-
54
- // Check that the tables exist
55
- const tables = db
56
- .query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
57
- .all() as { name: string }[];
58
- const tableNames = tables.map((t) => t.name);
59
-
60
- expect(tableNames).toContain("recordings");
61
- expect(tableNames).toContain("agents");
62
- expect(tableNames).toContain("projects");
63
- expect(tableNames).toContain("recording_tags");
64
- expect(tableNames).toContain("_migrations");
65
- });
66
-
67
- test("sets WAL journal mode", () => {
68
- const dbPath = join(tempDir, "test.db");
69
- const db = getDatabase(dbPath);
70
- const result = db.query("PRAGMA journal_mode").get() as { journal_mode: string } | null;
71
- expect(result?.journal_mode).toBe("wal");
72
- });
73
-
74
- test("enables foreign keys", () => {
75
- const dbPath = join(tempDir, "test.db");
76
- const db = getDatabase(dbPath);
77
- const result = db.query("PRAGMA foreign_keys").get() as { foreign_keys: number } | null;
78
- expect(result?.foreign_keys).toBe(1);
79
- });
80
-
81
- test("records migration level", () => {
82
- const dbPath = join(tempDir, "test.db");
83
- const db = getDatabase(dbPath);
84
- const result = db
85
- .query("SELECT MAX(id) as max_id FROM _migrations")
86
- .get() as { max_id: number };
87
- expect(result.max_id).toBe(0);
88
- });
89
-
90
- test("does not re-run migrations on second open", () => {
91
- const dbPath = join(tempDir, "test.db");
92
- const db1 = getDatabase(dbPath);
93
- // Insert a test row
94
- db1.query("INSERT INTO agents (id, name, created_at, last_seen_at) VALUES (?, ?, ?, ?)").run(
95
- "test-id",
96
- "test-agent",
97
- new Date().toISOString(),
98
- new Date().toISOString()
99
- );
100
- closeDatabase();
101
- resetDatabase();
102
-
103
- // Re-open
104
- const db2 = getDatabase(dbPath);
105
- const agent = db2.query("SELECT * FROM agents WHERE id = ?").get("test-id") as Record<string, unknown> | undefined;
106
- expect(agent).toBeDefined();
107
- expect(agent!["name"]).toBe("test-agent");
108
- });
109
- });
110
-
111
- describe("closeDatabase", () => {
112
- test("closes the database and allows reset", () => {
113
- const dbPath = join(tempDir, "test.db");
114
- getDatabase(dbPath);
115
- closeDatabase();
116
- resetDatabase();
117
-
118
- // Should be able to open a new database
119
- const dbPath2 = join(tempDir, "test2.db");
120
- const db2 = getDatabase(dbPath2);
121
- expect(db2).toBeDefined();
122
- });
123
-
124
- test("is a no-op when no database is open", () => {
125
- // Should not throw
126
- closeDatabase();
127
- });
128
- });
129
-
130
- describe("resetDatabase", () => {
131
- test("clears the singleton so next getDatabase creates new instance", () => {
132
- const dbPath1 = join(tempDir, "test1.db");
133
- const db1 = getDatabase(dbPath1);
134
- closeDatabase();
135
- resetDatabase();
136
-
137
- const dbPath2 = join(tempDir, "test2.db");
138
- const db2 = getDatabase(dbPath2);
139
- expect(db2).not.toBe(db1);
140
- });
141
- });
142
-
143
- describe("getDbPath", () => {
144
- test("returns the db_path from config", async () => {
145
- const { getDbPath } = await import("../db/database.js");
146
- const path = getDbPath();
147
- expect(typeof path).toBe("string");
148
- expect(path).toContain("recordings.db");
149
- });
150
- });
151
-
152
- describe("shortUuid", () => {
153
- test("returns an 8-character string", () => {
154
- const id = shortUuid();
155
- expect(id).toHaveLength(8);
156
- });
157
-
158
- test("returns different values on each call", () => {
159
- const ids = new Set(Array.from({ length: 100 }, () => shortUuid()));
160
- expect(ids.size).toBe(100);
161
- });
162
-
163
- test("only contains valid UUID characters", () => {
164
- const id = shortUuid();
165
- expect(id).toMatch(/^[0-9a-f]{8}$/);
166
- });
167
- });