dashcam 1.0.1-beta.25 → 1.0.1-beta.27

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/bin/dashcam.js CHANGED
@@ -136,7 +136,56 @@ async function recordingAction(options, command) {
136
136
  log('Use "dashcam status" to check progress');
137
137
  log('Use "dashcam stop" to stop recording and upload');
138
138
 
139
- process.exit(0);
139
+ // Keep the process alive so recording continues
140
+ // Set up graceful shutdown handlers
141
+ const handleShutdown = async (signal) => {
142
+ log(`\nReceived ${signal}, stopping recording...`);
143
+ try {
144
+ const result = await processManager.stopActiveRecording();
145
+
146
+ if (result) {
147
+ log('Recording stopped successfully');
148
+ log('Uploading recording...');
149
+
150
+ try {
151
+ const uploadResult = await upload(result.outputPath, {
152
+ title: options.title || 'Dashcam Recording',
153
+ description: description,
154
+ project: options.project || options.k,
155
+ duration: result.duration,
156
+ clientStartDate: result.clientStartDate,
157
+ apps: result.apps,
158
+ icons: result.icons,
159
+ gifPath: result.gifPath,
160
+ snapshotPath: result.snapshotPath
161
+ });
162
+
163
+ // Write upload result for stop command to read
164
+ processManager.writeUploadResult({
165
+ shareLink: uploadResult.shareLink,
166
+ replayId: uploadResult.replay?.id
167
+ });
168
+
169
+ log('📹 Watch your recording:', uploadResult.shareLink);
170
+ } catch (uploadError) {
171
+ logError('Upload failed:', uploadError.message);
172
+ log('Recording saved locally:', result.outputPath);
173
+ }
174
+ }
175
+
176
+ process.exit(0);
177
+ } catch (error) {
178
+ logError('Failed to stop recording:', error.message);
179
+ process.exit(1);
180
+ }
181
+ };
182
+
183
+ process.on('SIGINT', handleShutdown);
184
+ process.on('SIGTERM', handleShutdown);
185
+
186
+ // Keep process alive indefinitely until stopped
187
+ await new Promise(() => {}); // Wait forever
188
+
140
189
  } catch (error) {
141
190
  logError('Failed to start recording:', error.message);
142
191
  process.exit(1);
@@ -626,7 +675,7 @@ program
626
675
 
627
676
  if (options.recover) {
628
677
  // Try to recover from interrupted recording
629
- const tempFileInfoPath = path.join(process.cwd(), '.dashcam', 'temp-file.json');
678
+ const tempFileInfoPath = path.join(APP.configDir, 'temp-file.json');
630
679
 
631
680
  if (fs.existsSync(tempFileInfoPath)) {
632
681
  console.log('Found interrupted recording, attempting recovery...');
@@ -1,13 +1,8 @@
1
- import { spawn } from 'child_process';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
3
  import os from 'os';
5
- import { fileURLToPath } from 'url';
6
4
  import { logger } from './logger.js';
7
5
 
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
6
  // Use a fixed directory in the user's home directory for cross-process communication
12
7
  const PROCESS_DIR = path.join(os.homedir(), '.dashcam-cli');
13
8
  const PID_FILE = path.join(PROCESS_DIR, 'recording.pid');
@@ -156,48 +151,66 @@ class ProcessManager {
156
151
  return false;
157
152
  }
158
153
 
159
- // Recording is active, send SIGINT to trigger graceful shutdown
160
- logger.info('Stopping active recording process', { pid });
161
- process.kill(pid, 'SIGINT');
162
-
163
- // Wait for the process to actually finish and upload
164
- // Increase timeout to allow for upload to complete
165
- const maxWaitTime = 120000; // 2 minutes max to allow for upload
166
- const startWait = Date.now();
167
-
168
- while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
169
- await new Promise(resolve => setTimeout(resolve, 500));
170
- }
171
-
172
- if (this.isProcessRunning(pid)) {
173
- logger.warn('Process did not stop within timeout, forcing termination');
174
- process.kill(pid, 'SIGKILL');
175
- await new Promise(resolve => setTimeout(resolve, 1000));
176
- }
177
-
178
- // The background process handles stopRecording() internally via SIGINT
179
- // We just need to return the basic result from the status file
180
- if (status) {
181
- logger.info('Background recording stopped, returning status', {
182
- outputPath: status.outputPath,
183
- duration: Date.now() - status.startTime
184
- });
154
+ // Check if this is the same process (direct recording)
155
+ if (pid === process.pid) {
156
+ logger.info('Stopping recording in current process');
157
+
158
+ // Import recorder module
159
+ const { stopRecording: stopRecorderRecording } = await import('./recorder.js');
185
160
 
186
- const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
187
- const result = {
188
- outputPath: status.outputPath,
189
- gifPath: `${basePath}.gif`,
190
- snapshotPath: `${basePath}.png`,
191
- duration: Date.now() - status.startTime,
192
- clientStartDate: status.startTime,
193
- apps: [],
194
- logs: []
195
- };
161
+ // Stop recording directly
162
+ const result = await stopRecorderRecording();
163
+
164
+ // Update status to indicate recording stopped
165
+ this.writeStatus({
166
+ isRecording: false,
167
+ completedTime: Date.now(),
168
+ pid: process.pid
169
+ });
196
170
 
197
171
  this.cleanup({ preserveResult: true });
198
172
  return result;
199
173
  } else {
200
- throw new Error('No status information available for active recording');
174
+ // Different process - send signal (for backward compatibility if needed)
175
+ logger.info('Stopping active recording process', { pid });
176
+ process.kill(pid, 'SIGINT');
177
+
178
+ // Wait for the process to actually finish
179
+ const maxWaitTime = 120000; // 2 minutes max
180
+ const startWait = Date.now();
181
+
182
+ while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
183
+ await new Promise(resolve => setTimeout(resolve, 500));
184
+ }
185
+
186
+ if (this.isProcessRunning(pid)) {
187
+ logger.warn('Process did not stop within timeout, forcing termination');
188
+ process.kill(pid, 'SIGKILL');
189
+ await new Promise(resolve => setTimeout(resolve, 1000));
190
+ }
191
+
192
+ if (status) {
193
+ logger.info('Recording stopped, returning status', {
194
+ outputPath: status.outputPath,
195
+ duration: Date.now() - status.startTime
196
+ });
197
+
198
+ const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
199
+ const result = {
200
+ outputPath: status.outputPath,
201
+ gifPath: `${basePath}.gif`,
202
+ snapshotPath: `${basePath}.png`,
203
+ duration: Date.now() - status.startTime,
204
+ clientStartDate: status.startTime,
205
+ apps: [],
206
+ logs: []
207
+ };
208
+
209
+ this.cleanup({ preserveResult: true });
210
+ return result;
211
+ } else {
212
+ throw new Error('No status information available for active recording');
213
+ }
201
214
  }
202
215
  } catch (error) {
203
216
  logger.error('Failed to stop recording', { error });
@@ -213,67 +226,41 @@ class ProcessManager {
213
226
  throw new Error('Recording already in progress');
214
227
  }
215
228
 
216
- // Always run in background mode by spawning a detached process
217
- logger.info('Starting recording in background mode');
218
-
219
- // Get the path to the CLI binary
220
- const binPath = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
221
-
222
- logger.debug('Background process path', { binPath, exists: fs.existsSync(binPath) });
223
-
224
- // Create log files for background process
225
- const logDir = PROCESS_DIR;
226
- const stdoutLog = path.join(logDir, 'background-stdout.log');
227
- const stderrLog = path.join(logDir, 'background-stderr.log');
228
-
229
- const stdoutFd = fs.openSync(stdoutLog, 'a');
230
- const stderrFd = fs.openSync(stderrLog, 'a');
231
-
232
- // Spawn a detached process that will handle the recording
233
- const backgroundProcess = spawn(process.execPath, [
234
- binPath,
235
- JSON.stringify(options)
236
- ], {
237
- detached: true,
238
- stdio: ['ignore', stdoutFd, stderrFd], // Log stdout and stderr
239
- env: {
240
- ...process.env,
241
- DASHCAM_BACKGROUND: 'true'
242
- }
243
- });
229
+ // Import recorder module
230
+ const { startRecording: startRecorderRecording } = await import('./recorder.js');
244
231
 
245
- // Close the file descriptors in the parent process
246
- fs.closeSync(stdoutFd);
247
- fs.closeSync(stderrFd);
248
-
249
- // Get the background process PID before unreffing
250
- const backgroundPid = backgroundProcess.pid;
232
+ logger.info('Starting recording directly');
251
233
 
252
- // Allow the parent process to exit independently
253
- backgroundProcess.unref();
234
+ // Start recording using the recorder module
235
+ const recordingOptions = {
236
+ fps: parseInt(options.fps) || 10,
237
+ includeAudio: options.audio || false,
238
+ customOutputPath: options.output || null
239
+ };
254
240
 
255
- // Wait a moment for the background process to initialize
256
- await new Promise(resolve => setTimeout(resolve, 2000));
241
+ const result = await startRecorderRecording(recordingOptions);
257
242
 
258
- // Read the status file to get recording details
259
- const status = this.readStatus();
260
-
261
- if (!status || !status.isRecording) {
262
- throw new Error('Background process failed to start recording');
263
- }
243
+ // Write status to track the recording
244
+ this.writeStatus({
245
+ isRecording: true,
246
+ startTime: result.startTime,
247
+ options,
248
+ pid: process.pid,
249
+ outputPath: result.outputPath
250
+ });
264
251
 
265
- // Write PID file so other commands can find the background process
266
- this.writePid(status.pid);
252
+ // Write PID file so other commands can find the recording process
253
+ this.writePid(process.pid);
267
254
 
268
- logger.info('Background recording process started', {
269
- pid: status.pid,
270
- outputPath: status.outputPath
255
+ logger.info('Recording started successfully', {
256
+ pid: process.pid,
257
+ outputPath: result.outputPath
271
258
  });
272
259
 
273
260
  return {
274
- pid: status.pid,
275
- outputPath: status.outputPath,
276
- startTime: status.startTime
261
+ pid: process.pid,
262
+ outputPath: result.outputPath,
263
+ startTime: result.startTime
277
264
  };
278
265
  }
279
266
 
package/lib/recorder.js CHANGED
@@ -4,6 +4,7 @@ import { createGif, createSnapshot } from './ffmpeg.js';
4
4
  import { applicationTracker } from './applicationTracker.js';
5
5
  import { logsTrackerManager, trimLogs } from './logs/index.js';
6
6
  import { getFfmpegPath } from './binaries.js';
7
+ import { APP } from './config.js';
7
8
  import path from 'path';
8
9
  import os from 'os';
9
10
  import fs from 'fs';
@@ -145,8 +146,8 @@ let outputPath = null;
145
146
  let recordingStartTime = null;
146
147
  let currentTempFile = null;
147
148
 
148
- // File paths - use system temp for runtime data
149
- const DASHCAM_TEMP_DIR = path.join(os.tmpdir(), 'dashcam');
149
+ // File paths - use APP config directory for better Windows compatibility
150
+ const DASHCAM_TEMP_DIR = APP.configDir;
150
151
  const TEMP_FILE_INFO_PATH = path.join(DASHCAM_TEMP_DIR, 'temp-file.json');
151
152
 
152
153
  // Platform-specific configurations
@@ -238,12 +239,12 @@ async function getPlatformArgs({ fps, includeAudio }) {
238
239
  }
239
240
 
240
241
  /**
241
- * Clear the tmp/recordings directory
242
+ * Clear the recordings directory
242
243
  */
243
244
  function clearRecordingsDirectory() {
244
245
  const logExit = logFunctionCall('clearRecordingsDirectory');
245
246
 
246
- const directory = path.join(process.cwd(), 'tmp', 'recordings');
247
+ const directory = APP.recordingsDir;
247
248
 
248
249
  try {
249
250
  if (fs.existsSync(directory)) {
@@ -281,8 +282,8 @@ function generateOutputPath() {
281
282
  const logExit = logFunctionCall('generateOutputPath');
282
283
 
283
284
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
284
- // Use system temp directory with dashcam subdirectory
285
- const directory = path.join(os.tmpdir(), 'dashcam', 'recordings');
285
+ // Use APP recordings directory for consistent cross-platform location
286
+ const directory = APP.recordingsDir;
286
287
  const filepath = path.join(directory, `recording-${timestamp}.webm`);
287
288
 
288
289
  logger.verbose('Generating output path', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.25",
3
+ "version": "1.0.1-beta.27",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test script to verify system information collection
4
+ */
5
+
6
+ import { getSystemInfo } from './lib/systemInfo.js';
7
+ import { logger } from './lib/logger.js';
8
+
9
+ async function testSystemInfo() {
10
+ console.log('Testing system information collection...\n');
11
+
12
+ try {
13
+ const systemInfo = await getSystemInfo();
14
+
15
+ console.log('✓ System information collected successfully\n');
16
+ console.log('System Information:');
17
+ console.log('==================\n');
18
+
19
+ console.log('CPU:');
20
+ console.log(` Brand: ${systemInfo.cpu.brand}`);
21
+ console.log(` Cores: ${systemInfo.cpu.cores}`);
22
+ console.log(` Speed: ${systemInfo.cpu.speed} GHz`);
23
+ console.log();
24
+
25
+ console.log('Memory:');
26
+ console.log(` Total: ${(systemInfo.mem.total / (1024 ** 3)).toFixed(2)} GB`);
27
+ console.log(` Free: ${(systemInfo.mem.free / (1024 ** 3)).toFixed(2)} GB`);
28
+ console.log(` Used: ${(systemInfo.mem.used / (1024 ** 3)).toFixed(2)} GB`);
29
+ console.log();
30
+
31
+ console.log('Operating System:');
32
+ console.log(` Platform: ${systemInfo.os.platform}`);
33
+ console.log(` Distribution: ${systemInfo.os.distro}`);
34
+ console.log(` Release: ${systemInfo.os.release}`);
35
+ console.log(` Architecture: ${systemInfo.os.arch}`);
36
+ console.log(` Hostname: ${systemInfo.os.hostname}`);
37
+ console.log();
38
+
39
+ console.log('Graphics:');
40
+ console.log(` Controllers: ${systemInfo.graphics.controllers?.length || 0}`);
41
+ if (systemInfo.graphics.controllers?.length > 0) {
42
+ systemInfo.graphics.controllers.forEach((controller, index) => {
43
+ console.log(` ${index + 1}. ${controller.vendor} ${controller.model}`);
44
+ if (controller.vram) {
45
+ console.log(` VRAM: ${controller.vram} MB`);
46
+ }
47
+ });
48
+ }
49
+ console.log(` Displays: ${systemInfo.graphics.displays?.length || 0}`);
50
+ if (systemInfo.graphics.displays?.length > 0) {
51
+ systemInfo.graphics.displays.forEach((display, index) => {
52
+ console.log(` ${index + 1}. ${display.model || 'Unknown'}`);
53
+ console.log(` Resolution: ${display.currentResX}x${display.currentResY}`);
54
+ console.log(` Refresh Rate: ${display.currentRefreshRate} Hz`);
55
+ });
56
+ }
57
+ console.log();
58
+
59
+ console.log('System:');
60
+ console.log(` Manufacturer: ${systemInfo.system.manufacturer}`);
61
+ console.log(` Model: ${systemInfo.system.model}`);
62
+ console.log(` Virtual: ${systemInfo.system.virtual ? 'Yes' : 'No'}`);
63
+ console.log();
64
+
65
+ // Verify all required fields are present
66
+ console.log('Validation:');
67
+ console.log('===========\n');
68
+
69
+ const validations = [
70
+ { name: 'CPU info', valid: !!systemInfo.cpu && !!systemInfo.cpu.brand },
71
+ { name: 'Memory info', valid: !!systemInfo.mem && systemInfo.mem.total > 0 },
72
+ { name: 'OS info', valid: !!systemInfo.os && !!systemInfo.os.platform },
73
+ { name: 'Graphics info', valid: !!systemInfo.graphics },
74
+ { name: 'System info', valid: !!systemInfo.system }
75
+ ];
76
+
77
+ let allValid = true;
78
+ validations.forEach(v => {
79
+ const status = v.valid ? '✓' : '✗';
80
+ console.log(`${status} ${v.name}: ${v.valid ? 'OK' : 'MISSING'}`);
81
+ if (!v.valid) allValid = false;
82
+ });
83
+
84
+ console.log();
85
+
86
+ if (allValid) {
87
+ console.log('✓ All system information fields are properly populated');
88
+ console.log('✓ System information is ready to be uploaded to the API');
89
+ } else {
90
+ console.log('✗ Some system information is missing');
91
+ }
92
+
93
+ // Show the JSON structure that would be sent to the API
94
+ console.log('\nJSON Structure for API:');
95
+ console.log('======================\n');
96
+ console.log(JSON.stringify(systemInfo, null, 2));
97
+
98
+ } catch (error) {
99
+ console.error('✗ Failed to collect system information:', error.message);
100
+ console.error(error.stack);
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ testSystemInfo();
package/test_workflow.sh CHANGED
@@ -33,13 +33,16 @@ echo "✅ File tracking configured"
33
33
  # 4. Start dashcam recording in background
34
34
  echo ""
35
35
  echo "4. Starting dashcam recording in background..."
36
- # Start recording and redirect output to a log file so we can still monitor it
36
+ echo "⚠️ NOTE: Recording will run in the background using nohup"
37
+ echo ""
38
+
39
+ # Use nohup to properly detach the process and keep it running
37
40
  ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
38
41
  RECORD_PID=$!
39
42
 
40
43
  # Wait for recording to initialize and log tracker to start
41
44
  echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
42
- sleep 1
45
+ sleep 3
43
46
 
44
47
  # Write first event after log tracker is fully ready
45
48
  RECORDING_START=$(date +%s)
@@ -48,11 +51,12 @@ echo "🔴 EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
51
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49
52
  echo "[EVENT 1] 🔴 Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
53
 
51
- # Verify recording is actually running
52
- if ps -p $RECORD_PID > /dev/null; then
54
+ # Verify recording is actually running by checking status
55
+ if ./bin/dashcam.js status | grep -q "Recording in progress"; then
53
56
  echo "✅ Recording started successfully"
54
57
  else
55
- echo "❌ Recording process died, check /tmp/dashcam-recording.log"
58
+ echo "❌ Recording failed to start, check /tmp/dashcam-recording.log"
59
+ cat /tmp/dashcam-recording.log
56
60
  exit 1
57
61
  fi
58
62