dashcam 0.8.3 → 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.
- package/.dashcam/cli-config.json +3 -0
- package/.dashcam/recording.log +135 -0
- package/.dashcam/web-config.json +11 -0
- package/.github/RELEASE.md +59 -0
- package/.github/workflows/build.yml +103 -0
- package/.github/workflows/publish.yml +43 -0
- package/.github/workflows/release.yml +107 -0
- package/LOG_TRACKING_GUIDE.md +225 -0
- package/README.md +709 -155
- package/bin/dashcam.cjs +8 -0
- package/bin/dashcam.js +542 -0
- package/bin/index.js +63 -0
- package/examples/execute-script.js +152 -0
- package/examples/simple-test.js +37 -0
- package/lib/applicationTracker.js +311 -0
- package/lib/auth.js +222 -0
- package/lib/binaries.js +21 -0
- package/lib/config.js +34 -0
- package/lib/extension-logs/helpers.js +182 -0
- package/lib/extension-logs/index.js +347 -0
- package/lib/extension-logs/manager.js +344 -0
- package/lib/ffmpeg.js +156 -0
- package/lib/logTracker.js +23 -0
- package/lib/logger.js +118 -0
- package/lib/logs/index.js +432 -0
- package/lib/permissions.js +85 -0
- package/lib/processManager.js +255 -0
- package/lib/recorder.js +675 -0
- package/lib/store.js +58 -0
- package/lib/tracking/FileTracker.js +98 -0
- package/lib/tracking/FileTrackerManager.js +62 -0
- package/lib/tracking/LogsTracker.js +147 -0
- package/lib/tracking/active-win.js +212 -0
- package/lib/tracking/icons/darwin.js +39 -0
- package/lib/tracking/icons/index.js +167 -0
- package/lib/tracking/icons/windows.js +27 -0
- package/lib/tracking/idle.js +82 -0
- package/lib/tracking.js +23 -0
- package/lib/uploader.js +449 -0
- package/lib/utilities/jsonl.js +77 -0
- package/lib/webLogsDaemon.js +234 -0
- package/lib/websocket/server.js +223 -0
- package/package.json +50 -21
- package/recording.log +814 -0
- package/sea-bundle.mjs +34595 -0
- package/test-page.html +15 -0
- package/test.log +1 -0
- package/test_run.log +48 -0
- package/test_workflow.sh +80 -0
- package/examples/crash-test.js +0 -11
- package/examples/github-issue.sh +0 -1
- package/examples/protocol.html +0 -22
- package/index.js +0 -177
- package/lib.js +0 -199
- package/recorder.js +0 -85
package/bin/dashcam.cjs
ADDED
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();
|