@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,109 +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
- } from "../db/database.js";
10
- import { registerProject, getProject, listProjects } from "../db/projects.js";
11
- import { type Database } from "bun:sqlite";
12
-
13
- let tempDir: string;
14
- let db: Database;
15
-
16
- beforeEach(() => {
17
- resetDatabase();
18
- tempDir = join(tmpdir(), `open-recordings-test-proj-${Date.now()}-${Math.random().toString(36).slice(2)}`);
19
- mkdirSync(tempDir, { recursive: true });
20
- const dbPath = join(tempDir, "test.db");
21
- db = getDatabase(dbPath);
22
- });
23
-
24
- afterEach(() => {
25
- closeDatabase();
26
- resetDatabase();
27
- if (existsSync(tempDir)) {
28
- rmSync(tempDir, { recursive: true, force: true });
29
- }
30
- });
31
-
32
- describe("registerProject", () => {
33
- test("creates a new project", () => {
34
- const project = registerProject("my-app", "/home/user/my-app", undefined, db);
35
- expect(project).toBeDefined();
36
- expect(project.name).toBe("my-app");
37
- expect(project.path).toBe("/home/user/my-app");
38
- expect(project.description).toBeNull();
39
- expect(project.id).toBeDefined();
40
- expect(project.id).toHaveLength(36); // full UUID
41
- expect(project.created_at).toBeDefined();
42
- expect(project.updated_at).toBeDefined();
43
- });
44
-
45
- test("creates a project with description", () => {
46
- const project = registerProject("my-app", "/home/user/my-app", "A cool project", db);
47
- expect(project.description).toBe("A cool project");
48
- });
49
-
50
- test("returns existing project by path (idempotent)", () => {
51
- const first = registerProject("my-app", "/home/user/my-app", undefined, db);
52
- const second = registerProject("my-app", "/home/user/my-app", undefined, db);
53
-
54
- expect(second.id).toBe(first.id);
55
- expect(new Date(second.updated_at).getTime()).toBeGreaterThanOrEqual(
56
- new Date(first.updated_at).getTime()
57
- );
58
- });
59
-
60
- test("creates separate projects for different paths", () => {
61
- const p1 = registerProject("app-a", "/path/a", undefined, db);
62
- const p2 = registerProject("app-b", "/path/b", undefined, db);
63
-
64
- expect(p1.id).not.toBe(p2.id);
65
- expect(listProjects(db)).toHaveLength(2);
66
- });
67
- });
68
-
69
- describe("getProject", () => {
70
- test("finds project by ID", () => {
71
- const created = registerProject("test", "/tmp/test", undefined, db);
72
- const found = getProject(created.id, db);
73
- expect(found).toBeDefined();
74
- expect(found!.name).toBe("test");
75
- });
76
-
77
- test("finds project by path", () => {
78
- registerProject("finder", "/unique/path/here", undefined, db);
79
- const found = getProject("/unique/path/here", db);
80
- expect(found).toBeDefined();
81
- expect(found!.name).toBe("finder");
82
- });
83
-
84
- test("returns null for non-existent project", () => {
85
- const found = getProject("nonexistent", db);
86
- expect(found).toBeNull();
87
- });
88
- });
89
-
90
- describe("listProjects", () => {
91
- test("returns empty array when no projects", () => {
92
- const projects = listProjects(db);
93
- expect(projects).toEqual([]);
94
- });
95
-
96
- test("returns all projects ordered by updated_at DESC", () => {
97
- registerProject("alpha", "/path/alpha", undefined, db);
98
- registerProject("beta", "/path/beta", undefined, db);
99
- registerProject("gamma", "/path/gamma", undefined, db);
100
-
101
- const projects = listProjects(db);
102
- expect(projects).toHaveLength(3);
103
- // All three projects are present
104
- const names = projects.map((p) => p.name);
105
- expect(names).toContain("alpha");
106
- expect(names).toContain("beta");
107
- expect(names).toContain("gamma");
108
- });
109
- });
@@ -1,278 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test";
2
- import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { mkdirSync, rmSync, existsSync } from "fs";
5
- import { DEFAULT_CONFIG } from "../lib/config.js";
6
- import type { RecordingsConfig } from "../types/index.js";
7
- import { RecordingError } from "../types/index.js";
8
- import { EventEmitter } from "events";
9
-
10
- let tempDir: string;
11
-
12
- beforeEach(() => {
13
- tempDir = join(tmpdir(), `open-recordings-test-rec-${Date.now()}-${Math.random().toString(36).slice(2)}`);
14
- mkdirSync(tempDir, { recursive: true });
15
- });
16
-
17
- afterEach(() => {
18
- if (existsSync(tempDir)) {
19
- rmSync(tempDir, { recursive: true, force: true });
20
- }
21
- });
22
-
23
- describe("checkRecordingDeps", () => {
24
- test("returns an object with available, tool, and message fields", async () => {
25
- const { checkRecordingDeps } = await import("../lib/recorder.js");
26
- const result = await checkRecordingDeps();
27
- expect(result).toHaveProperty("available");
28
- expect(result).toHaveProperty("tool");
29
- expect(result).toHaveProperty("message");
30
- expect(typeof result.available).toBe("boolean");
31
- expect(typeof result.tool).toBe("string");
32
- expect(typeof result.message).toBe("string");
33
- });
34
-
35
- test("detects at least one recording tool on this system or returns none", async () => {
36
- const { checkRecordingDeps } = await import("../lib/recorder.js");
37
- const result = await checkRecordingDeps();
38
- if (result.available) {
39
- expect(["sox", "rec", "ffmpeg"]).toContain(result.tool);
40
- expect(result.message).toContain("is available");
41
- } else {
42
- expect(result.tool).toBe("none");
43
- expect(result.message).toContain("No recording tool found");
44
- expect(result.message).toContain("Install sox");
45
- }
46
- });
47
- });
48
-
49
- describe("isRecording", () => {
50
- test("returns false when not recording", async () => {
51
- const { isRecording } = await import("../lib/recorder.js");
52
- expect(isRecording()).toBe(false);
53
- });
54
- });
55
-
56
- describe("getCurrentFile", () => {
57
- test("returns null when not recording", async () => {
58
- const { getCurrentFile } = await import("../lib/recorder.js");
59
- expect(getCurrentFile()).toBeNull();
60
- });
61
- });
62
-
63
- describe("stopRecording", () => {
64
- test("returns null when not recording", async () => {
65
- const { stopRecording } = await import("../lib/recorder.js");
66
- const result = stopRecording();
67
- expect(result).toBeNull();
68
- });
69
- });
70
-
71
- describe("startRecording with mocked spawn", () => {
72
- test("spawns rec process and returns filepath", async () => {
73
- // Create a mock child process
74
- const mockProcess = new EventEmitter() as any;
75
- mockProcess.kill = mock(() => {});
76
- mockProcess.stdin = null;
77
- mockProcess.stdout = null;
78
- mockProcess.stderr = null;
79
-
80
- mock.module("child_process", () => ({
81
- spawn: mock(() => mockProcess),
82
- }));
83
-
84
- // Re-import to pick up mock
85
- const recorder = await import("../lib/recorder.js");
86
-
87
- // Reset any existing recording state by stopping
88
- recorder.stopRecording();
89
-
90
- const config: RecordingsConfig = {
91
- ...DEFAULT_CONFIG,
92
- audio_dir: tempDir,
93
- audio_format: "wav",
94
- sample_rate: 16000,
95
- max_recording_seconds: 300,
96
- };
97
-
98
- const filepath = recorder.startRecording(config);
99
- expect(filepath).toContain(tempDir);
100
- expect(filepath).toContain("recording-");
101
- expect(filepath).toContain(".wav");
102
- expect(recorder.isRecording()).toBe(true);
103
- expect(recorder.getCurrentFile()).toBe(filepath);
104
-
105
- // Stop
106
- const stopped = recorder.stopRecording();
107
- expect(stopped).toBe(filepath);
108
- expect(recorder.isRecording()).toBe(false);
109
- });
110
-
111
- test("throws RecordingError when already recording", async () => {
112
- const mockProcess = new EventEmitter() as any;
113
- mockProcess.kill = mock(() => {});
114
- mockProcess.stdin = null;
115
- mockProcess.stdout = null;
116
- mockProcess.stderr = null;
117
-
118
- mock.module("child_process", () => ({
119
- spawn: mock(() => mockProcess),
120
- }));
121
-
122
- const recorder = await import("../lib/recorder.js");
123
- recorder.stopRecording(); // Clean state
124
-
125
- const config: RecordingsConfig = {
126
- ...DEFAULT_CONFIG,
127
- audio_dir: tempDir,
128
- };
129
-
130
- recorder.startRecording(config);
131
-
132
- try {
133
- recorder.startRecording(config);
134
- expect(true).toBe(false);
135
- } catch (err) {
136
- expect(err).toBeInstanceOf(RecordingError);
137
- expect((err as Error).message).toContain("Already recording");
138
- }
139
-
140
- recorder.stopRecording();
141
- });
142
-
143
- test("generates filename with mp3 format", async () => {
144
- const mockProcess = new EventEmitter() as any;
145
- mockProcess.kill = mock(() => {});
146
- mockProcess.stdin = null;
147
- mockProcess.stdout = null;
148
- mockProcess.stderr = null;
149
-
150
- mock.module("child_process", () => ({
151
- spawn: mock(() => mockProcess),
152
- }));
153
-
154
- const recorder = await import("../lib/recorder.js");
155
- recorder.stopRecording();
156
-
157
- const config: RecordingsConfig = {
158
- ...DEFAULT_CONFIG,
159
- audio_dir: tempDir,
160
- audio_format: "mp3",
161
- };
162
-
163
- const filepath = recorder.startRecording(config);
164
- expect(filepath).toContain(".mp3");
165
-
166
- recorder.stopRecording();
167
- });
168
-
169
- test("handles process exit event", async () => {
170
- const mockProcess = new EventEmitter() as any;
171
- mockProcess.kill = mock(() => {});
172
- mockProcess.stdin = null;
173
- mockProcess.stdout = null;
174
- mockProcess.stderr = null;
175
-
176
- mock.module("child_process", () => ({
177
- spawn: mock(() => mockProcess),
178
- }));
179
-
180
- const recorder = await import("../lib/recorder.js");
181
- recorder.stopRecording();
182
-
183
- const config: RecordingsConfig = {
184
- ...DEFAULT_CONFIG,
185
- audio_dir: tempDir,
186
- };
187
-
188
- recorder.startRecording(config);
189
- expect(recorder.isRecording()).toBe(true);
190
-
191
- // Simulate process exit
192
- mockProcess.emit("exit", 0);
193
-
194
- // After exit, _recordProcess should be null but _currentFile may still be set
195
- // until stopRecording is called
196
- recorder.stopRecording();
197
- });
198
-
199
- test("handles process error event by clearing state", async () => {
200
- const mockProcess = new EventEmitter() as any;
201
- mockProcess.kill = mock(() => {});
202
- mockProcess.stdin = null;
203
- mockProcess.stdout = null;
204
- mockProcess.stderr = null;
205
-
206
- mock.module("child_process", () => ({
207
- spawn: mock(() => mockProcess),
208
- }));
209
-
210
- const recorder = await import("../lib/recorder.js");
211
- recorder.stopRecording();
212
-
213
- const config: RecordingsConfig = {
214
- ...DEFAULT_CONFIG,
215
- audio_dir: tempDir,
216
- };
217
-
218
- recorder.startRecording(config);
219
- expect(recorder.isRecording()).toBe(true);
220
-
221
- // Simulate process error - the error handler throws, but we catch it
222
- try {
223
- mockProcess.emit("error", new Error("spawn ENOENT"));
224
- } catch {
225
- // The error handler throws a RecordingError
226
- }
227
-
228
- // After error, state should be cleared
229
- // Note: the error handler sets _recordProcess = null and _currentFile = null
230
- // but since it throws, the state cleanup depends on the throw being caught
231
- recorder.stopRecording(); // Cleanup
232
- });
233
-
234
- test("stopRecording sends SIGINT to process", async () => {
235
- const mockProcess = new EventEmitter() as any;
236
- mockProcess.kill = mock(() => {});
237
- mockProcess.stdin = null;
238
- mockProcess.stdout = null;
239
- mockProcess.stderr = null;
240
-
241
- mock.module("child_process", () => ({
242
- spawn: mock(() => mockProcess),
243
- }));
244
-
245
- const recorder = await import("../lib/recorder.js");
246
- recorder.stopRecording();
247
-
248
- const config: RecordingsConfig = {
249
- ...DEFAULT_CONFIG,
250
- audio_dir: tempDir,
251
- };
252
-
253
- recorder.startRecording(config);
254
- recorder.stopRecording();
255
-
256
- expect(mockProcess.kill).toHaveBeenCalledWith("SIGINT");
257
- });
258
- });
259
-
260
- describe("recordDuration", () => {
261
- test("throws when rec is not available", async () => {
262
- const { recordDuration } = await import("../lib/recorder.js");
263
-
264
- const config: RecordingsConfig = {
265
- ...DEFAULT_CONFIG,
266
- audio_dir: tempDir,
267
- };
268
-
269
- try {
270
- await recordDuration(1, config);
271
- // If rec is installed, this might succeed
272
- } catch (err) {
273
- // Expected to fail since rec isn't typically installed in CI
274
- // Could be ENOENT from Bun.spawn or RecordingError
275
- expect(err).toBeDefined();
276
- }
277
- });
278
- });