dashcam 0.8.4 → 1.0.1-beta.2

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 (55) hide show
  1. package/.dashcam/cli-config.json +3 -0
  2. package/.dashcam/recording.log +135 -0
  3. package/.dashcam/web-config.json +11 -0
  4. package/.github/RELEASE.md +59 -0
  5. package/.github/workflows/build.yml +103 -0
  6. package/.github/workflows/publish.yml +43 -0
  7. package/.github/workflows/release.yml +107 -0
  8. package/LOG_TRACKING_GUIDE.md +225 -0
  9. package/README.md +709 -155
  10. package/bin/dashcam.cjs +8 -0
  11. package/bin/dashcam.js +542 -0
  12. package/bin/index.js +63 -0
  13. package/examples/execute-script.js +152 -0
  14. package/examples/simple-test.js +37 -0
  15. package/lib/applicationTracker.js +311 -0
  16. package/lib/auth.js +222 -0
  17. package/lib/binaries.js +21 -0
  18. package/lib/config.js +34 -0
  19. package/lib/extension-logs/helpers.js +182 -0
  20. package/lib/extension-logs/index.js +347 -0
  21. package/lib/extension-logs/manager.js +344 -0
  22. package/lib/ffmpeg.js +156 -0
  23. package/lib/logTracker.js +23 -0
  24. package/lib/logger.js +118 -0
  25. package/lib/logs/index.js +432 -0
  26. package/lib/permissions.js +85 -0
  27. package/lib/processManager.js +255 -0
  28. package/lib/recorder.js +675 -0
  29. package/lib/store.js +58 -0
  30. package/lib/tracking/FileTracker.js +98 -0
  31. package/lib/tracking/FileTrackerManager.js +62 -0
  32. package/lib/tracking/LogsTracker.js +147 -0
  33. package/lib/tracking/active-win.js +212 -0
  34. package/lib/tracking/icons/darwin.js +39 -0
  35. package/lib/tracking/icons/index.js +167 -0
  36. package/lib/tracking/icons/windows.js +27 -0
  37. package/lib/tracking/idle.js +82 -0
  38. package/lib/tracking.js +23 -0
  39. package/lib/uploader.js +449 -0
  40. package/lib/utilities/jsonl.js +77 -0
  41. package/lib/webLogsDaemon.js +234 -0
  42. package/lib/websocket/server.js +223 -0
  43. package/package.json +50 -21
  44. package/recording.log +814 -0
  45. package/sea-bundle.mjs +34595 -0
  46. package/test-page.html +15 -0
  47. package/test.log +1 -0
  48. package/test_run.log +48 -0
  49. package/test_workflow.sh +80 -0
  50. package/examples/crash-test.js +0 -11
  51. package/examples/github-issue.sh +0 -1
  52. package/examples/protocol.html +0 -22
  53. package/index.js +0 -158
  54. package/lib.js +0 -199
  55. package/recorder.js +0 -85
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ // CommonJS wrapper for pkg compatibility
4
+ // This file imports the ES module version
5
+ import('./dashcam.js').catch((err) => {
6
+ console.error('Failed to load dashcam:', err);
7
+ process.exit(1);
8
+ });
package/bin/dashcam.js ADDED
@@ -0,0 +1,542 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { auth } from '../lib/auth.js';
4
+ import { upload } from '../lib/uploader.js';
5
+ import { logger, setVerbose } from '../lib/logger.js';
6
+ import { APP } from '../lib/config.js';
7
+ import { createPattern } from '../lib/tracking.js';
8
+ import { processManager } from '../lib/processManager.js';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname } from 'path';
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ // Ensure config directory exists
18
+ if (!fs.existsSync(APP.configDir)) {
19
+ fs.mkdirSync(APP.configDir, { recursive: true });
20
+ }
21
+
22
+ // Ensure recordings directory exists
23
+ if (!fs.existsSync(APP.recordingsDir)) {
24
+ fs.mkdirSync(APP.recordingsDir, { recursive: true });
25
+ }
26
+
27
+ program
28
+ .name('dashcam')
29
+ .description('CLI version of Dashcam screen recorder')
30
+ .version(APP.version)
31
+ .option('-v, --verbose', 'Enable verbose logging output')
32
+ .hook('preAction', (thisCommand) => {
33
+ // Enable verbose logging if the flag is set
34
+ if (thisCommand.opts().verbose) {
35
+ setVerbose(true);
36
+ logger.info('Verbose logging enabled');
37
+ }
38
+ });
39
+
40
+ program
41
+ .command('auth')
42
+ .description('Authenticate with TestDriver using an API key')
43
+ .argument('<apiKey>', 'Your TestDriver API key')
44
+ .action(async (apiKey, options, command) => {
45
+ try {
46
+ logger.verbose('Starting authentication process', {
47
+ apiKeyProvided: !!apiKey,
48
+ globalOptions: command.parent.opts()
49
+ });
50
+
51
+ await auth.login(apiKey);
52
+ console.log('Successfully authenticated with API key');
53
+ process.exit(0);
54
+ } catch (error) {
55
+ console.error('Authentication failed:', error.message);
56
+ logger.error('Authentication failed with details:', {
57
+ error: error.message,
58
+ stack: error.stack
59
+ });
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ program
65
+ .command('logout')
66
+ .description('Logout from your Dashcam account')
67
+ .action(async () => {
68
+ try {
69
+ await auth.logout();
70
+ console.log('Successfully logged out');
71
+ process.exit(0);
72
+ } catch (error) {
73
+ logger.error('Logout failed:', error);
74
+ process.exit(1);
75
+ }
76
+ });
77
+
78
+ program
79
+ .command('record')
80
+ .description('Start a background screen recording')
81
+ .option('-a, --audio', 'Include audio in the recording')
82
+ .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
83
+ .option('-o, --output <path>', 'Custom output path')
84
+ .option('-t, --title <title>', 'Title for the recording')
85
+ .option('-d, --description <description>', 'Description for the recording (supports markdown)')
86
+ .option('-p, --project <project>', 'Project ID to upload the recording to')
87
+ .action(async (options, command) => {
88
+ try {
89
+ // Check if recording is already active
90
+ if (processManager.isRecordingActive()) {
91
+ const status = processManager.getActiveStatus();
92
+ const duration = ((Date.now() - status.startTime) / 1000).toFixed(1);
93
+ console.log('Recording already in progress');
94
+ console.log(`Duration: ${duration} seconds`);
95
+ console.log(`PID: ${status.pid}`);
96
+ console.log('Use "dashcam stop" to stop the recording');
97
+ process.exit(0);
98
+ }
99
+
100
+ // Check authentication
101
+ if (!await auth.isAuthenticated()) {
102
+ console.log('You need to login first. Run: dashcam auth <api-key>');
103
+ process.exit(1);
104
+ }
105
+
106
+ // Check screen recording permissions (macOS only)
107
+ const { ensurePermissions } = await import('../lib/permissions.js');
108
+ const hasPermissions = await ensurePermissions();
109
+ if (!hasPermissions) {
110
+ console.log('\n⚠️ Cannot start recording without screen recording permission.');
111
+ process.exit(1);
112
+ }
113
+
114
+ // Always use background mode
115
+ console.log('Starting recording...');
116
+
117
+ try {
118
+ const result = await processManager.startRecording({
119
+ fps: parseInt(options.fps) || 30,
120
+ audio: options.audio,
121
+ output: options.output,
122
+ title: options.title,
123
+ description: options.description,
124
+ project: options.project
125
+ });
126
+
127
+ console.log(`Recording started successfully (PID: ${result.pid})`);
128
+ console.log(`Output: ${result.outputPath}`);
129
+ console.log('Use "dashcam status" to check progress');
130
+ console.log('Use "dashcam stop" to stop recording and upload');
131
+
132
+ // Keep this process alive for background recording
133
+ console.log('Recording is running in background...');
134
+
135
+ // Set up signal handlers for graceful shutdown
136
+ let isShuttingDown = false;
137
+ const handleShutdown = async (signal) => {
138
+ if (isShuttingDown) {
139
+ console.log('Shutdown already in progress...');
140
+ return;
141
+ }
142
+ isShuttingDown = true;
143
+
144
+ console.log(`\nReceived ${signal}, stopping background recording...`);
145
+ try {
146
+ // Stop the recording using the recorder directly (not processManager)
147
+ const { stopRecording } = await import('../lib/recorder.js');
148
+ const stopResult = await stopRecording();
149
+
150
+ if (stopResult) {
151
+ console.log('Recording stopped:', stopResult.outputPath);
152
+
153
+ // Import and call upload function with the correct format
154
+ const { upload } = await import('../lib/uploader.js');
155
+
156
+ console.log('Starting upload...');
157
+ await upload(stopResult.outputPath, {
158
+ title: options.title || 'Dashcam Recording',
159
+ description: options.description || 'Recorded with Dashcam CLI',
160
+ project: options.project,
161
+ duration: stopResult.duration,
162
+ clientStartDate: stopResult.clientStartDate,
163
+ apps: stopResult.apps,
164
+ logs: stopResult.logs,
165
+ gifPath: stopResult.gifPath,
166
+ snapshotPath: stopResult.snapshotPath
167
+ });
168
+
169
+ console.log('Upload completed successfully!');
170
+ }
171
+
172
+ // Clean up process files
173
+ processManager.cleanup();
174
+ } catch (error) {
175
+ console.error('Error during shutdown:', error.message);
176
+ logger.error('Error during shutdown:', error);
177
+ }
178
+ process.exit(0);
179
+ };
180
+
181
+ process.on('SIGINT', () => handleShutdown('SIGINT'));
182
+ process.on('SIGTERM', () => handleShutdown('SIGTERM'));
183
+
184
+ // Keep the process alive
185
+ await new Promise(() => {});
186
+ } catch (error) {
187
+ console.error('Failed to start recording:', error.message);
188
+ process.exit(1);
189
+ }
190
+ } catch (error) {
191
+ logger.error('Failed to start recording:', error);
192
+ console.error('Failed to start recording:', error.message);
193
+ process.exit(1);
194
+ }
195
+ });
196
+
197
+ program
198
+ .command('status')
199
+ .description('Show current recording status')
200
+ .action(() => {
201
+ const activeStatus = processManager.getActiveStatus();
202
+ if (activeStatus) {
203
+ const duration = ((Date.now() - activeStatus.startTime) / 1000).toFixed(1);
204
+ console.log('Recording in progress');
205
+ console.log(`Duration: ${duration} seconds`);
206
+ console.log(`PID: ${activeStatus.pid}`);
207
+ console.log(`Started: ${new Date(activeStatus.startTime).toLocaleString()}`);
208
+ if (activeStatus.options.title) {
209
+ console.log(`Title: ${activeStatus.options.title}`);
210
+ }
211
+ } else {
212
+ console.log('No active recording');
213
+ }
214
+ process.exit(0);
215
+ });
216
+
217
+
218
+
219
+ program
220
+ .command('track')
221
+ .description('Track logs from web URLs or application files')
222
+ .option('--web <pattern>', 'Web URL pattern to track (can use wildcards like *)')
223
+ .option('--app <pattern>', 'Application file pattern to track (can use wildcards like *)')
224
+ .option('--name <name>', 'Name for the tracking configuration')
225
+ .action(async (options) => {
226
+ try {
227
+ // Validate that at least one pattern is provided
228
+ if (!options.web && !options.app) {
229
+ console.error('Error: Must provide either --web or --app pattern');
230
+ process.exit(1);
231
+ }
232
+
233
+ if (options.web) {
234
+ const config = {
235
+ name: options.name || 'Web Pattern',
236
+ type: 'web',
237
+ patterns: [options.web],
238
+ enabled: true
239
+ };
240
+
241
+ await createPattern(config);
242
+ console.log('Web tracking pattern added successfully:', options.web);
243
+ }
244
+
245
+ if (options.app) {
246
+ const config = {
247
+ name: options.name || 'App Pattern',
248
+ type: 'application',
249
+ patterns: [options.app],
250
+ enabled: true
251
+ };
252
+
253
+ await createPattern(config);
254
+ console.log('Application tracking pattern added successfully:', options.app);
255
+ }
256
+ process.exit(0);
257
+ } catch (error) {
258
+ console.error('Failed to add tracking pattern:', error.message);
259
+ process.exit(1);
260
+ }
261
+ });
262
+
263
+ program
264
+ .command('stop')
265
+ .description('Stop the current recording and wait for upload completion')
266
+ .action(async () => {
267
+ try {
268
+ // Enable verbose logging for stop command
269
+ setVerbose(true);
270
+
271
+ if (!processManager.isRecordingActive()) {
272
+ console.log('No active recording to stop');
273
+ process.exit(0);
274
+ }
275
+
276
+ const activeStatus = processManager.getActiveStatus();
277
+ const logFile = path.join(process.cwd(), '.dashcam', 'recording.log');
278
+
279
+ console.log('Stopping recording...');
280
+
281
+ try {
282
+ const result = await processManager.stopActiveRecording();
283
+
284
+ if (!result) {
285
+ console.log('Failed to stop recording');
286
+ process.exit(1);
287
+ }
288
+
289
+ console.log('Recording stopped successfully');
290
+ console.log('Output saved to:', result.outputPath);
291
+
292
+ // Check if files still exist - if not, background process already uploaded
293
+ const filesExist = fs.existsSync(result.outputPath) &&
294
+ (!result.gifPath || fs.existsSync(result.gifPath)) &&
295
+ (!result.snapshotPath || fs.existsSync(result.snapshotPath));
296
+
297
+ if (!filesExist) {
298
+ console.log('✅ Recording was already uploaded by background process');
299
+ console.log('✅ Recording stopped and uploaded');
300
+ process.exit(0);
301
+ }
302
+
303
+ // Always attempt to upload - let upload function find project if needed
304
+ console.log('Uploading recording...');
305
+ try {
306
+ const uploadResult = await upload(result.outputPath, {
307
+ title: activeStatus?.options?.title,
308
+ description: activeStatus?.options?.description,
309
+ project: activeStatus?.options?.project, // May be undefined, that's ok
310
+ duration: result.duration,
311
+ clientStartDate: result.clientStartDate,
312
+ apps: result.apps,
313
+ icons: result.icons,
314
+ gifPath: result.gifPath,
315
+ snapshotPath: result.snapshotPath
316
+ });
317
+
318
+ console.log('✅ Upload complete! Share link:', uploadResult.shareLink);
319
+ } catch (uploadError) {
320
+ console.error('Upload failed:', uploadError.message);
321
+ console.log('Recording saved locally:', result.outputPath);
322
+ }
323
+ } catch (error) {
324
+ console.error('Failed to stop recording:', error.message);
325
+ process.exit(1);
326
+ }
327
+
328
+ process.exit(0);
329
+ } catch (error) {
330
+ logger.error('Error stopping recording:', error);
331
+ console.error('Failed to stop recording:', error.message);
332
+ process.exit(1);
333
+ }
334
+ });
335
+
336
+ program
337
+ .command('logs')
338
+ .description('Manage log tracking for recordings')
339
+ .option('--add', 'Add a new log tracker')
340
+ .option('--remove <id>', 'Remove a log tracker by ID')
341
+ .option('--list', 'List all configured log trackers')
342
+ .option('--status', 'Show log tracking status')
343
+ .option('--name <name>', 'Name for the log tracker (required with --add)')
344
+ .option('--type <type>', 'Type of tracker: "web" or "file" (required with --add)')
345
+ .option('--pattern <pattern>', 'Pattern to track (can be used multiple times)', (value, previous) => {
346
+ return previous ? previous.concat([value]) : [value];
347
+ })
348
+ .option('--file <file>', 'File path for file type trackers')
349
+ .action(async (options) => {
350
+ try {
351
+ // Import logsTrackerManager only when needed to avoid unwanted initialization
352
+ const { logsTrackerManager } = await import('../lib/logs/index.js');
353
+
354
+ if (options.add) {
355
+ // Validate required options for add
356
+ if (!options.name) {
357
+ console.error('Error: --name is required when adding a tracker');
358
+ console.log('Example: dashcam logs --add --name=social --type=web --pattern="*facebook.com*"');
359
+ process.exit(1);
360
+ }
361
+ if (!options.type) {
362
+ console.error('Error: --type is required when adding a tracker (web or file)');
363
+ process.exit(1);
364
+ }
365
+ if (options.type !== 'web' && options.type !== 'file') {
366
+ console.error('Error: --type must be either "web" or "file"');
367
+ process.exit(1);
368
+ }
369
+
370
+ if (options.type === 'web') {
371
+ if (!options.pattern || options.pattern.length === 0) {
372
+ console.error('Error: At least one --pattern is required for web trackers');
373
+ console.log('Example: dashcam logs --add --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
374
+ process.exit(1);
375
+ }
376
+
377
+ const webConfig = {
378
+ id: options.name.toLowerCase().replace(/[^a-z0-9]/g, '-'),
379
+ name: options.name,
380
+ type: 'web',
381
+ enabled: true,
382
+ patterns: options.pattern
383
+ };
384
+
385
+ logsTrackerManager.addWebTracker(webConfig);
386
+ console.log(`Added web tracker "${options.name}" with patterns:`, options.pattern);
387
+ } else if (options.type === 'file') {
388
+ if (!options.file) {
389
+ console.error('Error: --file is required for file trackers');
390
+ console.log('Example: dashcam logs --add --name=app-logs --type=file --file=/var/log/app.log');
391
+ process.exit(1);
392
+ }
393
+ if (!fs.existsSync(options.file)) {
394
+ console.error('Log file does not exist:', options.file);
395
+ process.exit(1);
396
+ }
397
+
398
+ logsTrackerManager.addCliLogFile(options.file);
399
+ console.log(`Added file tracker "${options.name}" for:`, options.file);
400
+ }
401
+ } else if (options.remove) {
402
+ logsTrackerManager.removeTracker(options.remove);
403
+ console.log('Removed tracker:', options.remove);
404
+ } else if (options.list) {
405
+ const status = logsTrackerManager.getStatus();
406
+ console.log('Currently configured trackers:');
407
+
408
+ if (status.cliFiles.length > 0) {
409
+ console.log('\nFile trackers:');
410
+ status.cliFiles.forEach((filePath, index) => {
411
+ console.log(` file-${index + 1}: ${filePath}`);
412
+ });
413
+ }
414
+
415
+ if (status.webApps.length > 0) {
416
+ console.log('\nWeb trackers:');
417
+ status.webApps.forEach(app => {
418
+ console.log(` ${app.id}: ${app.name}`);
419
+ console.log(` Patterns: ${app.patterns.join(', ')}`);
420
+ });
421
+ }
422
+
423
+ if (status.cliFiles.length === 0 && status.webApps.length === 0) {
424
+ console.log(' (none configured)');
425
+ console.log('\nExamples:');
426
+ console.log(' dashcam logs --add --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
427
+ console.log(' dashcam logs --add --name=app-logs --type=file --file=/var/log/app.log');
428
+ }
429
+ } else if (options.status) {
430
+ const status = logsTrackerManager.getStatus();
431
+ console.log('Log tracking status:');
432
+ console.log(` Active recording instances: ${status.activeInstances}`);
433
+ console.log(` File trackers: ${status.cliFilesCount}`);
434
+ console.log(` Web trackers: ${status.webAppsCount}`);
435
+ console.log(` Total recent events: ${status.totalEvents}`);
436
+
437
+ if (status.fileTrackerStats.length > 0) {
438
+ console.log('\n File tracker activity (last minute):');
439
+ status.fileTrackerStats.forEach(stat => {
440
+ console.log(` ${stat.filePath}: ${stat.count} events`);
441
+ });
442
+ }
443
+ } else {
444
+ console.log('Please specify an action: --add, --remove, --list, or --status');
445
+ console.log('\nExamples:');
446
+ console.log(' dashcam logs --add --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
447
+ console.log(' dashcam logs --add --name=app-logs --type=file --file=/var/log/app.log');
448
+ console.log(' dashcam logs --list');
449
+ console.log(' dashcam logs --status');
450
+ console.log('\nUse "dashcam logs --help" for more information');
451
+ }
452
+
453
+ // Exit successfully to prevent hanging
454
+ process.exit(0);
455
+ } catch (error) {
456
+ logger.error('Error managing logs:', error);
457
+ console.error('Failed to manage logs:', error.message);
458
+ process.exit(1);
459
+ }
460
+ });
461
+
462
+ program
463
+ .command('upload')
464
+ .description('Upload a completed recording file or recover from interrupted recording')
465
+ .argument('[filePath]', 'Path to the recording file to upload (optional)')
466
+ .option('-t, --title <title>', 'Title for the recording')
467
+ .option('-d, --description <description>', 'Description for the recording')
468
+ .option('-p, --project <project>', 'Project ID to upload to')
469
+ .option('--recover', 'Attempt to recover and upload from interrupted recording')
470
+ .action(async (filePath, options) => {
471
+ try {
472
+ let targetFile = filePath;
473
+
474
+ if (options.recover) {
475
+ // Try to recover from interrupted recording
476
+ const tempFileInfoPath = path.join(process.cwd(), '.dashcam', 'temp-file.json');
477
+
478
+ if (fs.existsSync(tempFileInfoPath)) {
479
+ console.log('Found interrupted recording, attempting recovery...');
480
+
481
+ const tempFileInfo = JSON.parse(fs.readFileSync(tempFileInfoPath, 'utf8'));
482
+ const tempFile = tempFileInfo.tempFile;
483
+
484
+ if (fs.existsSync(tempFile) && fs.statSync(tempFile).size > 0) {
485
+ console.log('Recovering recording from temp file...');
486
+
487
+ // Import recorder to finalize the interrupted recording
488
+ const { stopRecording } = await import('../lib/recorder.js');
489
+
490
+ try {
491
+ // This will attempt to finalize the temp file
492
+ const result = await stopRecording();
493
+ targetFile = result.outputPath;
494
+ console.log('Recovery successful:', result.outputPath);
495
+ } catch (error) {
496
+ console.error('Recovery failed:', error.message);
497
+ console.log('You can try uploading the temp file directly:', tempFile);
498
+ targetFile = tempFile;
499
+ }
500
+
501
+ // Clean up temp file info after recovery attempt
502
+ fs.unlinkSync(tempFileInfoPath);
503
+ } else {
504
+ console.log('No valid temp file found for recovery');
505
+ process.exit(1);
506
+ }
507
+ } else {
508
+ console.log('No interrupted recording found');
509
+ process.exit(1);
510
+ }
511
+ }
512
+
513
+ if (!targetFile) {
514
+ console.error('Please provide a file path or use --recover option');
515
+ console.log('Examples:');
516
+ console.log(' dashcam upload /path/to/recording.webm');
517
+ console.log(' dashcam upload --recover');
518
+ process.exit(1);
519
+ }
520
+
521
+ if (!fs.existsSync(targetFile)) {
522
+ console.error('File not found:', targetFile);
523
+ process.exit(1);
524
+ }
525
+
526
+ console.log('Uploading recording...');
527
+ const uploadResult = await upload(targetFile, {
528
+ title: options.title,
529
+ description: options.description,
530
+ project: options.project
531
+ });
532
+
533
+ console.log('✅ Upload complete! Share link:', uploadResult.shareLink);
534
+ process.exit(0);
535
+
536
+ } catch (error) {
537
+ console.error('Upload failed:', error.message);
538
+ process.exit(1);
539
+ }
540
+ });
541
+
542
+ program.parse();
package/bin/index.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { login } from '../lib/auth.js';
5
+ import { startRecording, stopRecording } from '../lib/recorder.js';
6
+ import { uploadRecording } from '../lib/uploader.js';
7
+ import { logger } from '../lib/logger.js';
8
+
9
+ program
10
+ .name('dashcam')
11
+ .description('CLI screen recorder with automatic upload')
12
+ .version('1.0.0');
13
+
14
+ program
15
+ .command('login')
16
+ .description('Authenticate with Auth0')
17
+ .action(async () => {
18
+ try {
19
+ await login();
20
+ logger.info('Successfully logged in');
21
+ } catch (error) {
22
+ logger.error('Login failed:', error);
23
+ process.exit(1);
24
+ }
25
+ });
26
+
27
+ program
28
+ .command('record')
29
+ .description('Start recording the screen')
30
+ .option('-d, --duration <seconds>', 'Recording duration in seconds')
31
+ .action(async (options) => {
32
+ try {
33
+ await startRecording(options);
34
+ logger.info('Recording started');
35
+
36
+ if (options.duration) {
37
+ setTimeout(async () => {
38
+ const recordingPath = await stopRecording();
39
+ logger.info('Recording stopped');
40
+ await uploadRecording(recordingPath);
41
+ }, options.duration * 1000);
42
+ }
43
+ } catch (error) {
44
+ logger.error('Recording failed:', error);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ program
50
+ .command('stop')
51
+ .description('Stop the current recording and upload it')
52
+ .action(async () => {
53
+ try {
54
+ const recordingPath = await stopRecording();
55
+ logger.info('Recording stopped');
56
+ await uploadRecording(recordingPath);
57
+ } catch (error) {
58
+ logger.error('Failed to stop recording:', error);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ program.parse();