dashcam 1.0.1-beta.8 → 1.0.2-beta.1

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.
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test script for analyzing short recording issues
5
+ *
6
+ * This tests whether very short recordings produce valid multi-frame videos
7
+ * with properly finalized WebM container metadata.
8
+ *
9
+ * Known issue: If ffmpeg/VP9 encoder is killed too quickly, the WebM container
10
+ * metadata (especially duration) may be incomplete, causing playback issues.
11
+ *
12
+ * Usage:
13
+ * node test-short-recording.js # Run recording tests
14
+ * node test-short-recording.js analyze <file> # Analyze existing video
15
+ * node test-short-recording.js fix <input> <output> # Fix broken video container
16
+ *
17
+ * Platform notes:
18
+ * - macOS: Uses AVFoundation for screen capture
19
+ * - Linux: Uses X11grab for screen capture
20
+ * - Windows: Uses gdigrab for screen capture
21
+ */
22
+
23
+ import { startRecording, stopRecording, fixVideoContainer } from './lib/recorder.js';
24
+ import { execa } from 'execa';
25
+ import { getFfprobePath } from './lib/binaries.js';
26
+ import fs from 'fs';
27
+ import path from 'path';
28
+ import os from 'os';
29
+
30
+ async function analyzeVideo(videoPath) {
31
+ const ffprobePath = await getFfprobePath();
32
+
33
+ console.log(`\nšŸ“Š Analyzing video: ${videoPath}`);
34
+ console.log('─'.repeat(80));
35
+
36
+ // Check if file exists
37
+ if (!fs.existsSync(videoPath)) {
38
+ console.error(`āŒ Video file does not exist: ${videoPath}`);
39
+ return null;
40
+ }
41
+
42
+ const stats = fs.statSync(videoPath);
43
+ console.log(`šŸ“ File size: ${(stats.size / 1024).toFixed(2)} KB`);
44
+
45
+ try {
46
+ // Get basic format info
47
+ const formatResult = await execa(ffprobePath, [
48
+ '-v', 'error',
49
+ '-show_entries', 'format=duration,size,bit_rate',
50
+ '-of', 'json',
51
+ videoPath
52
+ ]);
53
+
54
+ const formatData = JSON.parse(formatResult.stdout);
55
+ console.log(`ā±ļø Duration: ${formatData.format.duration || 'unknown'}s`);
56
+ console.log(`šŸ“Š Bit rate: ${formatData.format.bit_rate || 'unknown'} bits/s`);
57
+
58
+ // Get stream info
59
+ const streamResult = await execa(ffprobePath, [
60
+ '-v', 'error',
61
+ '-show_entries', 'stream=codec_name,width,height,r_frame_rate,duration',
62
+ '-of', 'json',
63
+ videoPath
64
+ ]);
65
+
66
+ const streamData = JSON.parse(streamResult.stdout);
67
+ const videoStream = streamData.streams.find(s => s.codec_name);
68
+
69
+ if (videoStream) {
70
+ console.log(`šŸŽ„ Codec: ${videoStream.codec_name}`);
71
+ console.log(`šŸ“ Resolution: ${videoStream.width}x${videoStream.height}`);
72
+ console.log(`šŸŽžļø Frame rate: ${videoStream.r_frame_rate}`);
73
+ }
74
+
75
+ // Count actual frames
76
+ const frameResult = await execa(ffprobePath, [
77
+ '-v', 'error',
78
+ '-count_frames',
79
+ '-select_streams', 'v:0',
80
+ '-show_entries', 'stream=nb_read_frames',
81
+ '-of', 'default=nokey=1:noprint_wrappers=1',
82
+ videoPath
83
+ ], { reject: false });
84
+
85
+ const frameCount = parseInt(frameResult.stdout.trim());
86
+ console.log(`šŸ–¼ļø Frame count: ${frameCount || 'unknown'}`);
87
+
88
+ if (frameResult.stderr) {
89
+ console.log(`āš ļø FFprobe warnings: ${frameResult.stderr.trim()}`);
90
+ }
91
+
92
+ // Check if duration is available in container
93
+ const hasDuration = formatData.format.duration && !isNaN(parseFloat(formatData.format.duration));
94
+
95
+ // Determine if this is a single-frame video issue
96
+ const isSingleFrame = frameCount === 1;
97
+ const hasEncodingIssues = frameResult.stderr.includes('File ended prematurely');
98
+ const hasMissingMetadata = !hasDuration;
99
+
100
+ console.log('\nšŸ“‹ Analysis Result:');
101
+ console.log(` Single frame: ${isSingleFrame ? 'āŒ YES (BUG!)' : 'āœ… NO'}`);
102
+ console.log(` Encoding issues: ${hasEncodingIssues ? 'āš ļø YES' : 'āœ… NO'}`);
103
+ console.log(` Missing metadata: ${hasMissingMetadata ? 'āš ļø YES (container incomplete)' : 'āœ… NO'}`);
104
+ console.log(` Platform: ${os.platform()}`);
105
+
106
+ return {
107
+ exists: true,
108
+ size: stats.size,
109
+ duration: parseFloat(formatData.format.duration),
110
+ frameCount,
111
+ codec: videoStream?.codec_name,
112
+ resolution: videoStream ? `${videoStream.width}x${videoStream.height}` : 'unknown',
113
+ isSingleFrame,
114
+ hasEncodingIssues,
115
+ hasMissingMetadata,
116
+ platform: os.platform()
117
+ };
118
+
119
+ } catch (error) {
120
+ console.error(`āŒ Error analyzing video: ${error.message}`);
121
+ return null;
122
+ }
123
+ }
124
+
125
+ async function testShortRecording(duration = 3000) {
126
+ console.log(`\nšŸŽ¬ Testing ${duration}ms recording...`);
127
+ console.log('═'.repeat(80));
128
+
129
+ try {
130
+ // Start recording
131
+ console.log('ā–¶ļø Starting recording...');
132
+ const { outputPath, startTime } = await startRecording({
133
+ fps: 30,
134
+ includeAudio: false
135
+ });
136
+
137
+ console.log(`āœ… Recording started at: ${outputPath}`);
138
+
139
+ // Wait for specified duration
140
+ console.log(`ā³ Recording for ${duration}ms...`);
141
+ await new Promise(resolve => setTimeout(resolve, duration));
142
+
143
+ // Stop recording
144
+ console.log('ā¹ļø Stopping recording...');
145
+ const result = await stopRecording();
146
+
147
+ console.log(`āœ… Recording stopped`);
148
+ console.log(` Duration: ${result.duration}ms`);
149
+ console.log(` File: ${result.outputPath}`);
150
+
151
+ // Analyze the output
152
+ await analyzeVideo(result.outputPath);
153
+
154
+ return result;
155
+
156
+ } catch (error) {
157
+ console.error(`āŒ Test failed: ${error.message}`);
158
+ console.error(error.stack);
159
+ throw error;
160
+ }
161
+ }
162
+
163
+ async function testExistingVideo(videoPath) {
164
+ console.log('\nšŸ” Testing existing video...');
165
+ console.log('═'.repeat(80));
166
+
167
+ return await analyzeVideo(videoPath);
168
+ }
169
+
170
+ // Main test runner
171
+ async function main() {
172
+ const args = process.argv.slice(2);
173
+
174
+ console.log('\n🧪 Short Recording Test Suite');
175
+ console.log('═'.repeat(80));
176
+ console.log(`Platform: ${os.platform()}`);
177
+ console.log(`Architecture: ${os.arch()}`);
178
+ console.log(`Node version: ${process.version}`);
179
+
180
+ if (args[0] === 'analyze' && args[1]) {
181
+ // Analyze existing video
182
+ const videoPath = path.resolve(args[1]);
183
+ const result = await testExistingVideo(videoPath);
184
+
185
+ if (result?.isSingleFrame) {
186
+ console.log('\nāŒ SINGLE-FRAME VIDEO DETECTED!');
187
+ process.exit(1);
188
+ } else if (result?.hasMissingMetadata) {
189
+ console.log('\nāš ļø WARNING: Video container metadata is incomplete!');
190
+ console.log(' This can cause playback issues in some players.');
191
+ console.log(' The video has frames but duration is not in the container.');
192
+ console.log('\nšŸ’” Try fixing it with:');
193
+ console.log(` node test-short-recording.js fix ${args[1]} ${args[1].replace(/\.(webm|mp4)$/, '-fixed.$1')}`);
194
+ process.exit(1);
195
+ }
196
+ } else if (args[0] === 'fix' && args[1] && args[2]) {
197
+ // Fix existing broken video
198
+ const inputPath = path.resolve(args[1]);
199
+ const outputPath = path.resolve(args[2]);
200
+
201
+ console.log('\nšŸ”§ Fixing video container...');
202
+ console.log('═'.repeat(80));
203
+ console.log(`Input: ${inputPath}`);
204
+ console.log(`Output: ${outputPath}`);
205
+
206
+ if (!fs.existsSync(inputPath)) {
207
+ console.error(`āŒ Input file does not exist: ${inputPath}`);
208
+ process.exit(1);
209
+ }
210
+
211
+ // Analyze before
212
+ console.log('\nšŸ“Š BEFORE:');
213
+ const beforeResult = await analyzeVideo(inputPath);
214
+
215
+ // Fix the video
216
+ const fixSuccess = await fixVideoContainer(inputPath, outputPath);
217
+
218
+ if (!fixSuccess) {
219
+ console.error('\nāŒ Failed to fix video!');
220
+ process.exit(1);
221
+ }
222
+
223
+ // Analyze after
224
+ console.log('\nšŸ“Š AFTER:');
225
+ const afterResult = await analyzeVideo(outputPath);
226
+
227
+ console.log('\nāœ… Video fixed successfully!');
228
+ console.log(` Before: ${beforeResult?.hasMissingMetadata ? 'Missing metadata āš ļø' : 'Has metadata āœ…'}`);
229
+ console.log(` After: ${afterResult?.hasMissingMetadata ? 'Missing metadata āš ļø' : 'Has metadata āœ…'}`);
230
+
231
+ if (afterResult?.hasMissingMetadata) {
232
+ console.log('\nāš ļø Warning: Metadata still missing after fix. The source file may be corrupted.');
233
+ process.exit(1);
234
+ }
235
+ } else {
236
+ // Run recording tests with different durations
237
+ const testDurations = [1000, 2000, 3000, 5000];
238
+ const results = [];
239
+
240
+ for (const duration of testDurations) {
241
+ try {
242
+ const result = await testShortRecording(duration);
243
+ results.push({ duration, success: true, result });
244
+
245
+ // Clean up
246
+ try {
247
+ fs.unlinkSync(result.outputPath);
248
+ if (result.gifPath && fs.existsSync(result.gifPath)) {
249
+ fs.unlinkSync(result.gifPath);
250
+ }
251
+ if (result.snapshotPath && fs.existsSync(result.snapshotPath)) {
252
+ fs.unlinkSync(result.snapshotPath);
253
+ }
254
+ } catch (cleanupError) {
255
+ console.warn(`āš ļø Cleanup warning: ${cleanupError.message}`);
256
+ }
257
+ } catch (error) {
258
+ results.push({ duration, success: false, error: error.message });
259
+ }
260
+
261
+ // Wait between tests
262
+ await new Promise(resolve => setTimeout(resolve, 2000));
263
+ }
264
+
265
+ // Summary
266
+ console.log('\n\nšŸ“Š TEST SUMMARY');
267
+ console.log('═'.repeat(80));
268
+
269
+ for (const result of results) {
270
+ const status = result.success ? 'āœ…' : 'āŒ';
271
+ console.log(`${status} ${result.duration}ms recording: ${result.success ? 'PASSED' : result.error}`);
272
+ }
273
+
274
+ const allPassed = results.every(r => r.success);
275
+ if (!allPassed) {
276
+ console.log('\nāŒ Some tests failed!');
277
+ process.exit(1);
278
+ } else {
279
+ console.log('\nāœ… All tests passed!');
280
+ }
281
+ }
282
+ }
283
+
284
+ main().catch(error => {
285
+ console.error('Fatal error:', error);
286
+ process.exit(1);
287
+ });
package/test_workflow.sh CHANGED
@@ -46,7 +46,7 @@ RECORDING_START=$(date +%s)
46
46
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
47
47
  echo "šŸ”“ EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
48
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49
- echo "[EVENT 1] Recording started at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
49
+ echo "[EVENT 1] šŸ”“ Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
50
 
51
51
  # Verify recording is actually running
52
52
  if ps -p $RECORD_PID > /dev/null; then
@@ -71,25 +71,25 @@ sleep 3
71
71
  # Event 2 - after 3 seconds
72
72
  echo ""
73
73
  echo "🟔 EVENT 2: 3 seconds mark at $(date '+%H:%M:%S')"
74
- echo "[EVENT 2] 3 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
74
+ echo "[EVENT 2] 🟔 3 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
75
75
  sleep 3
76
76
 
77
77
  # Event 3 - after 6 seconds
78
78
  echo ""
79
79
  echo "🟢 EVENT 3: 6 seconds mark at $(date '+%H:%M:%S')"
80
- echo "[EVENT 3] 6 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
80
+ echo "[EVENT 3] 🟢 6 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
81
81
  sleep 3
82
82
 
83
83
  # Event 4 - after 9 seconds
84
84
  echo ""
85
85
  echo "šŸ”µ EVENT 4: 9 seconds mark at $(date '+%H:%M:%S')"
86
- echo "[EVENT 4] 9 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
86
+ echo "[EVENT 4] šŸ”µ 9 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
87
87
  sleep 3
88
88
 
89
89
  # Event 5 - after 12 seconds
90
90
  echo ""
91
91
  echo "🟣 EVENT 5: 12 seconds mark at $(date '+%H:%M:%S')"
92
- echo "[EVENT 5] 12 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
92
+ echo "[EVENT 5] 🟣 12 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
93
93
  sleep 3
94
94
 
95
95
  # Event 6 - before ending
@@ -98,7 +98,7 @@ echo "━━━━━━━━━━━━━━━━━━━━━━━━
98
98
  echo "⚫ EVENT 6: Recording END at $(date '+%H:%M:%S')"
99
99
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
100
100
  RECORDING_END=$(date +%s)
101
- echo "[EVENT 6] Recording ending at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_END" >> "$TEMP_FILE"
101
+ echo "[EVENT 6] ⚫ Recording ending with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_END" >> "$TEMP_FILE"
102
102
 
103
103
  DURATION=$((RECORDING_END - RECORDING_START))
104
104
  echo ""
@@ -112,8 +112,13 @@ sleep 2
112
112
  # 6. Stop recording and upload (this will kill the background recording process)
113
113
  echo ""
114
114
  echo "6. Stopping recording and uploading..."
115
- ./bin/dashcam.js stop
116
- echo "āœ… Recording stopped and uploaded"
115
+ # Check if recording is still active
116
+ if ./bin/dashcam.js status | grep -q "Recording in progress"; then
117
+ ./bin/dashcam.js stop
118
+ echo "āœ… Recording stopped and uploaded"
119
+ else
120
+ echo "āš ļø Recording already completed (this is expected with background mode)"
121
+ fi
117
122
 
118
123
  echo ""
119
124
  echo "🧹 Cleaning up..."
@@ -140,12 +145,12 @@ echo "3. Verify these events appear at the correct times:"
140
145
  echo ""
141
146
  echo " Time | Terminal Display | Log Entry"
142
147
  echo " -------|---------------------------|---------------------------"
143
- echo " 0:00 | šŸ”“ EVENT 1 | [EVENT 1] Recording started"
144
- echo " 0:03 | 🟔 EVENT 2 | [EVENT 2] 3 seconds elapsed"
145
- echo " 0:06 | 🟢 EVENT 3 | [EVENT 3] 6 seconds elapsed"
146
- echo " 0:09 | šŸ”µ EVENT 4 | [EVENT 4] 9 seconds elapsed"
147
- echo " 0:12 | 🟣 EVENT 5 | [EVENT 5] 12 seconds elapsed"
148
- echo " 0:15 | ⚫ EVENT 6 | [EVENT 6] Recording ending"
148
+ echo " 0:00 | šŸ”“ EVENT 1 | [EVENT 1] šŸ”“ Recording started"
149
+ echo " 0:03 | 🟔 EVENT 2 | [EVENT 2] 🟔 3 seconds elapsed"
150
+ echo " 0:06 | 🟢 EVENT 3 | [EVENT 3] 🟢 6 seconds elapsed"
151
+ echo " 0:09 | šŸ”µ EVENT 4 | [EVENT 4] šŸ”µ 9 seconds elapsed"
152
+ echo " 0:12 | 🟣 EVENT 5 | [EVENT 5] 🟣 12 seconds elapsed"
153
+ echo " 0:15 | ⚫ EVENT 6 | [EVENT 6] ⚫ Recording ending"
149
154
  echo ""
150
155
  echo "4. The log timestamps should match the video timeline exactly"
151
156
  echo "5. Each colored event marker should appear in the video"
@@ -1,177 +0,0 @@
1
- # Backward Compatibility Summary
2
-
3
- This document confirms that `dashcam-cli-minimal` now supports all commands and arguments documented in the README.md.
4
-
5
- ## āœ… Implemented Commands
6
-
7
- ### `auth <api-key>`
8
- Authenticate the dashcam desktop using a team's apiKey.
9
- ```bash
10
- dashcam auth <api-key>
11
- ```
12
-
13
- ### `create [options]`
14
- Create a clip from current recording and output the resulting url or markdown. This stops the current recording and uploads it.
15
- ```bash
16
- # Start instant replay in background
17
- dashcam start
18
-
19
- # Later, create a clip from the recording
20
- dashcam create
21
- dashcam create -t "My New Title"
22
- dashcam create --md
23
- dashcam create -k wef8we72h23012j
24
- dashcam create -d "Description text"
25
- cat README.md | dashcam create
26
- ```
27
-
28
- Options:
29
- - `-t, --title <string>` - Title of the replay
30
- - `-d, --description [text]` - Replay markdown body (supports piped input)
31
- - `--md` - Returns rich markdown image link
32
- - `-k, --project <project>` - Project ID to publish to
33
-
34
- **Note:** `create` stops the current recording and creates a clip. It's similar to `stop` but focused on outputting URLs/markdown for integration with other tools.
35
-
36
- ### `record [options]`
37
- Start a recording terminal to be included in your dashcam video recording.
38
- ```bash
39
- dashcam record
40
- ```
41
-
42
- Options:
43
- - `-t, --title <title>` - Title for the recording
44
- - `-d, --description <description>` - Description for the recording
45
- - `-p, --project <project>` - Project ID to upload to
46
- - `-a, --audio` - Include audio
47
- - `-f, --fps <fps>` - Frames per second
48
-
49
- ### `pipe`
50
- Pipe command output to dashcam to be included in recorded video.
51
- ```bash
52
- ping 1.1.1.1 | dashcam pipe
53
- cat /var/log/system.log | dashcam pipe
54
- ```
55
-
56
- ### `track [options]`
57
- Add a logs config to Dashcam.
58
-
59
- **New Syntax (matches README):**
60
- ```bash
61
- dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"
62
- dashcam track --name=app-logs --type=application --pattern="/var/log/*.log"
63
- ```
64
-
65
- **Old Syntax (still supported):**
66
- ```bash
67
- dashcam track --web "*facebook.com*"
68
- dashcam track --app "/var/log/app.log"
69
- ```
70
-
71
- Options:
72
- - `--name <name>` - Name for the tracking configuration (required with new syntax)
73
- - `--type <type>` - Type: "application" or "web" (required with new syntax)
74
- - `--pattern <pattern>` - Pattern to track (can use multiple times)
75
- - `--web <pattern>` - Web URL pattern (deprecated, use --type=web --pattern)
76
- - `--app <pattern>` - Application file pattern (deprecated, use --type=application --pattern)
77
-
78
- ### `start`
79
- Start instant replay recording on dashcam.
80
- ```bash
81
- dashcam start
82
- ```
83
-
84
- ### `stop`
85
- Stop the current recording and upload.
86
- ```bash
87
- dashcam stop
88
- ```
89
-
90
- ### `status`
91
- Show current recording status.
92
- ```bash
93
- dashcam status
94
- ```
95
-
96
- ## Examples from README
97
-
98
- All examples from the README should now work:
99
-
100
- ### Basic usage
101
- ```bash
102
- # Create a replay
103
- dashcam create
104
- # Returns: https://dashcam.io/replay/123?share=xyz
105
-
106
- # With markdown output
107
- dashcam create --md
108
-
109
- # With title
110
- dashcam create -t "My New Title"
111
-
112
- # With project
113
- dashcam create -k wef8we72h23012j
114
-
115
- # Attach last 20 CLI commands
116
- history -20 | dashcam create
117
-
118
- # Attach a logfile
119
- cat /var/log/system.log | dashcam create
120
- ```
121
-
122
- ### Tracking logs
123
- ```bash
124
- # Track web URLs
125
- dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"
126
-
127
- # Track application files
128
- dashcam track --name=app-logs --type=application --pattern="/var/log/*.log"
129
- ```
130
-
131
- ### Recording
132
- ```bash
133
- # Start recording
134
- dashcam record
135
-
136
- # Pipe output into recording
137
- ping 1.1.1.1 | dashcam pipe
138
-
139
- # Stop recording
140
- dashcam stop
141
- ```
142
-
143
- ### GitHub CLI integration
144
- ```bash
145
- # Create GitHub issue with replay
146
- gh issue create -w -t "Title" -b "`dashcam create --md`"
147
-
148
- # With system logs
149
- gh issue create -w -t "Title" -b "`cat /var/log/system.log | dashcam create --md`"
150
-
151
- # Create PR with replay
152
- gh pr create -w -t "Title" -b "`dashcam create --md`"
153
-
154
- # Append to commit
155
- git commit -am "`dashcam create`"
156
- ```
157
-
158
- ## Key Changes for Backward Compatibility
159
-
160
- 1. **Added `create` command** - Stops current recording and creates a clip with URL/markdown output
161
- 2. **Added `pipe` command** - Allows piping command output into recordings
162
- 3. **Added `start` command** - Simple way to start instant replay recording in background
163
- 4. **Updated `track` command** - Now supports both old syntax (--web, --app) and new syntax (--name, --type, --pattern)
164
- 5. **Updated descriptions** - Match README text exactly
165
- 6. **Updated `auth` parameter** - Changed from `<apiKey>` to `<api-key>` to match README
166
- 7. **Added `-k` alias** - For `--project` option in `create` command
167
- 8. **Shared implementation** - `create`, `record`, and `start` share common code to avoid duplication
168
-
169
- ## Migration Notes
170
-
171
- - **`start`** - Starts instant replay recording in background (like desktop app's always-on recording)
172
- - **`create`** - Stops the current recording and outputs URL/markdown (perfect for CI/CD, git hooks, GitHub CLI)
173
- - **`record`** - Full-featured recording command with all options (terminal recording mode)
174
- - **`stop`** - Similar to `create` but focused on stopping and uploading vs URL output
175
- - Old `track` syntax still works for backward compatibility but new syntax is preferred
176
- - All piped input examples from README are supported
177
- - Can run just `dashcam` with options (defaults to `create` command)