dashcam 1.0.1-beta.5 → 1.0.1-beta.6

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
@@ -26,7 +26,7 @@ if (!fs.existsSync(APP.recordingsDir)) {
26
26
 
27
27
  program
28
28
  .name('dashcam')
29
- .description('CLI version of Dashcam screen recorder')
29
+ .description('Capture the steps to reproduce every bug.')
30
30
  .version(APP.version)
31
31
  .option('-v, --verbose', 'Enable verbose logging output')
32
32
  .hook('preAction', (thisCommand) => {
@@ -39,8 +39,8 @@ program
39
39
 
40
40
  program
41
41
  .command('auth')
42
- .description('Authenticate with TestDriver using an API key')
43
- .argument('<apiKey>', 'Your TestDriver API key')
42
+ .description("Authenticate the dashcam desktop using a team's apiKey")
43
+ .argument('<api-key>', 'Your team API key')
44
44
  .action(async (apiKey, options, command) => {
45
45
  try {
46
46
  logger.verbose('Starting authentication process', {
@@ -75,133 +75,267 @@ program
75
75
  }
76
76
  });
77
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
- .option('-s, --silent', 'Silent mode - suppress all output')
88
- .action(async (options, command) => {
78
+ // Shared recording action to avoid duplication
79
+ async function recordingAction(options, command) {
80
+ try {
81
+ const silent = options.silent;
82
+ const log = (...args) => { if (!silent) console.log(...args); };
83
+ const logError = (...args) => { if (!silent) console.error(...args); };
84
+
85
+ // Check if recording is already active
86
+ if (processManager.isRecordingActive()) {
87
+ const status = processManager.getActiveStatus();
88
+ const duration = ((Date.now() - status.startTime) / 1000).toFixed(1);
89
+ log('Recording already in progress');
90
+ log(`Duration: ${duration} seconds`);
91
+ log(`PID: ${status.pid}`);
92
+ log('Use "dashcam stop" to stop the recording');
93
+ process.exit(0);
94
+ }
95
+
96
+ // Check authentication
97
+ if (!await auth.isAuthenticated()) {
98
+ log('You need to login first. Run: dashcam auth <api-key>');
99
+ process.exit(1);
100
+ }
101
+
102
+ // Check for piped input (description from stdin) if description option not set
103
+ let description = options.description;
104
+ if (!description && !process.stdin.isTTY) {
105
+ const chunks = [];
106
+ for await (const chunk of process.stdin) {
107
+ chunks.push(chunk);
108
+ }
109
+ description = Buffer.concat(chunks).toString('utf-8');
110
+ }
111
+
112
+ // Check screen recording permissions (macOS only)
113
+ const { ensurePermissions } = await import('../lib/permissions.js');
114
+ const hasPermissions = await ensurePermissions();
115
+ if (!hasPermissions) {
116
+ log('\n⚠️ Cannot start recording without screen recording permission.');
117
+ process.exit(1);
118
+ }
119
+
120
+ // Always use background mode
121
+ log('Starting recording...');
122
+
89
123
  try {
90
- const silent = options.silent;
91
- const log = (...args) => { if (!silent) console.log(...args); };
92
- const logError = (...args) => { if (!silent) console.error(...args); };
124
+ const result = await processManager.startRecording({
125
+ fps: parseInt(options.fps) || 30,
126
+ audio: options.audio,
127
+ output: options.output,
128
+ title: options.title,
129
+ description: description,
130
+ project: options.project || options.k // Support both -p and -k for project
131
+ });
132
+
133
+ log(`Recording started successfully (PID: ${result.pid})`);
134
+ log(`Output: ${result.outputPath}`);
135
+ log('Use "dashcam status" to check progress');
136
+ log('Use "dashcam stop" to stop recording and upload');
137
+
138
+ // Keep this process alive for background recording
139
+ log('Recording is running in background...');
93
140
 
94
- // Check if recording is already active
95
- if (processManager.isRecordingActive()) {
96
- const status = processManager.getActiveStatus();
97
- const duration = ((Date.now() - status.startTime) / 1000).toFixed(1);
98
- log('Recording already in progress');
99
- log(`Duration: ${duration} seconds`);
100
- log(`PID: ${status.pid}`);
101
- log('Use "dashcam stop" to stop the recording');
141
+ // Set up signal handlers for graceful shutdown
142
+ let isShuttingDown = false;
143
+ const handleShutdown = async (signal) => {
144
+ if (isShuttingDown) {
145
+ log('Shutdown already in progress...');
146
+ return;
147
+ }
148
+ isShuttingDown = true;
149
+
150
+ log(`\nReceived ${signal}, stopping background recording...`);
151
+ try {
152
+ // Stop the recording using the recorder directly (not processManager)
153
+ const { stopRecording } = await import('../lib/recorder.js');
154
+ const stopResult = await stopRecording();
155
+
156
+ if (stopResult) {
157
+ log('Recording stopped:', stopResult.outputPath);
158
+
159
+ // Import and call upload function with the correct format
160
+ const { upload } = await import('../lib/uploader.js');
161
+
162
+ log('Starting upload...');
163
+ const uploadResult = await upload(stopResult.outputPath, {
164
+ title: options.title || 'Dashcam Recording',
165
+ description: description || 'Recorded with Dashcam CLI',
166
+ project: options.project || options.k,
167
+ duration: stopResult.duration,
168
+ clientStartDate: stopResult.clientStartDate,
169
+ apps: stopResult.apps,
170
+ logs: stopResult.logs,
171
+ gifPath: stopResult.gifPath,
172
+ snapshotPath: stopResult.snapshotPath
173
+ });
174
+
175
+ // Write upload result for stop command to read
176
+ processManager.writeUploadResult({
177
+ shareLink: uploadResult.shareLink,
178
+ replayId: uploadResult.replay?.id
179
+ });
180
+
181
+ // Output based on format option (for create/markdown mode)
182
+ if (options.md) {
183
+ const replayId = uploadResult.replay?.id;
184
+ const shareKey = uploadResult.shareLink.split('share=')[1];
185
+ log(`[![Dashcam - ${options.title || 'New Replay'}](https://replayable-api-production.herokuapp.com/replay/${replayId}/gif?shareKey=${shareKey})](${uploadResult.shareLink})`);
186
+ log('');
187
+ log(`Watch [Dashcam - ${options.title || 'New Replay'}](${uploadResult.shareLink}) on Dashcam`);
188
+ } else {
189
+ log('✅ Upload complete!');
190
+ log('📹 Watch your recording:', uploadResult.shareLink);
191
+ }
192
+ }
193
+
194
+ // Clean up process files, but preserve upload result for stop command
195
+ processManager.cleanup({ preserveResult: true });
196
+ } catch (error) {
197
+ logError('Error during shutdown:', error.message);
198
+ logger.error('Error during shutdown:', error);
199
+ }
102
200
  process.exit(0);
201
+ };
202
+
203
+ process.on('SIGINT', () => handleShutdown('SIGINT'));
204
+ process.on('SIGTERM', () => handleShutdown('SIGTERM'));
205
+
206
+ // Keep the process alive
207
+ await new Promise(() => {});
208
+ } catch (error) {
209
+ logError('Failed to start recording:', error.message);
210
+ process.exit(1);
211
+ }
212
+ } catch (error) {
213
+ logger.error('Failed to start recording:', error);
214
+ if (!options.silent) console.error('Failed to start recording:', error.message);
215
+ process.exit(1);
216
+ }
217
+ }
218
+
219
+ // 'create' command - creates a clip from current recording (like stop but with more options)
220
+ program
221
+ .command('create')
222
+ .description('Create a clip and output the resulting url or markdown. Will launch desktop app for local editing before publishing.')
223
+ .option('-t, --title <string>', 'Title of the replay. Automatically generated if not supplied.')
224
+ .option('-d, --description [text]', 'Replay markdown body. This may also be piped in: `cat README.md | dashcam create`')
225
+ .option('--md', 'Returns code for a rich markdown image link.')
226
+ .option('-k, --project <project>', 'Project ID to publish to')
227
+ .action(async (options) => {
228
+ try {
229
+ // Check for piped input (description from stdin)
230
+ let description = options.description;
231
+ if (!description && !process.stdin.isTTY) {
232
+ const chunks = [];
233
+ for await (const chunk of process.stdin) {
234
+ chunks.push(chunk);
235
+ }
236
+ description = Buffer.concat(chunks).toString('utf-8');
103
237
  }
104
238
 
105
- // Check authentication
106
- if (!await auth.isAuthenticated()) {
107
- log('You need to login first. Run: dashcam auth <api-key>');
108
- process.exit(1);
239
+ if (!processManager.isRecordingActive()) {
240
+ console.log('No active recording to create clip from');
241
+ console.log('Start a recording first with "dashcam record" or "dashcam start"');
242
+ process.exit(0);
109
243
  }
110
244
 
111
- // Check screen recording permissions (macOS only)
112
- const { ensurePermissions } = await import('../lib/permissions.js');
113
- const hasPermissions = await ensurePermissions();
114
- if (!hasPermissions) {
115
- log('\n⚠️ Cannot start recording without screen recording permission.');
245
+ const activeStatus = processManager.getActiveStatus();
246
+
247
+ console.log('Creating clip from recording...');
248
+
249
+ const result = await processManager.stopActiveRecording();
250
+
251
+ if (!result) {
252
+ console.log('Failed to stop recording');
116
253
  process.exit(1);
117
254
  }
118
255
 
119
- // Always use background mode
120
- log('Starting recording...');
256
+ console.log('Recording stopped successfully');
121
257
 
258
+ // Upload the recording
259
+ console.log('Uploading clip...');
122
260
  try {
123
- const result = await processManager.startRecording({
124
- fps: parseInt(options.fps) || 30,
125
- audio: options.audio,
126
- output: options.output,
127
- title: options.title,
128
- description: options.description,
129
- project: options.project
261
+ const uploadResult = await upload(result.outputPath, {
262
+ title: options.title || activeStatus?.options?.title || 'Dashcam Recording',
263
+ description: description || activeStatus?.options?.description,
264
+ project: options.project || options.k || activeStatus?.options?.project,
265
+ duration: result.duration,
266
+ clientStartDate: result.clientStartDate,
267
+ apps: result.apps,
268
+ icons: result.icons,
269
+ gifPath: result.gifPath,
270
+ snapshotPath: result.snapshotPath
130
271
  });
131
-
132
- log(`Recording started successfully (PID: ${result.pid})`);
133
- log(`Output: ${result.outputPath}`);
134
- log('Use "dashcam status" to check progress');
135
- log('Use "dashcam stop" to stop recording and upload');
136
-
137
- // Keep this process alive for background recording
138
- log('Recording is running in background...');
139
-
140
- // Set up signal handlers for graceful shutdown
141
- let isShuttingDown = false;
142
- const handleShutdown = async (signal) => {
143
- if (isShuttingDown) {
144
- log('Shutdown already in progress...');
145
- return;
146
- }
147
- isShuttingDown = true;
148
-
149
- log(`\nReceived ${signal}, stopping background recording...`);
150
- try {
151
- // Stop the recording using the recorder directly (not processManager)
152
- const { stopRecording } = await import('../lib/recorder.js');
153
- const stopResult = await stopRecording();
154
-
155
- if (stopResult) {
156
- log('Recording stopped:', stopResult.outputPath);
157
-
158
- // Import and call upload function with the correct format
159
- const { upload } = await import('../lib/uploader.js');
160
-
161
- log('Starting upload...');
162
- const uploadResult = await upload(stopResult.outputPath, {
163
- title: options.title || 'Dashcam Recording',
164
- description: options.description || 'Recorded with Dashcam CLI',
165
- project: options.project,
166
- duration: stopResult.duration,
167
- clientStartDate: stopResult.clientStartDate,
168
- apps: stopResult.apps,
169
- logs: stopResult.logs,
170
- gifPath: stopResult.gifPath,
171
- snapshotPath: stopResult.snapshotPath
172
- });
173
-
174
- // Write upload result for stop command to read
175
- processManager.writeUploadResult({
176
- shareLink: uploadResult.shareLink,
177
- replayId: uploadResult.replay?.id
178
- });
179
-
180
- log('✅ Upload complete!');
181
- log('📹 Watch your recording:', uploadResult.shareLink);
182
- }
183
-
184
- // Clean up process files, but preserve upload result for stop command
185
- processManager.cleanup({ preserveResult: true });
186
- } catch (error) {
187
- logError('Error during shutdown:', error.message);
188
- logger.error('Error during shutdown:', error);
189
- }
190
- process.exit(0);
191
- };
192
272
 
193
- process.on('SIGINT', () => handleShutdown('SIGINT'));
194
- process.on('SIGTERM', () => handleShutdown('SIGTERM'));
195
-
196
- // Keep the process alive
197
- await new Promise(() => {});
198
- } catch (error) {
199
- logError('Failed to start recording:', error.message);
273
+ // Output based on format option
274
+ if (options.md) {
275
+ const replayId = uploadResult.replay?.id;
276
+ const shareKey = uploadResult.shareLink.split('share=')[1];
277
+ console.log(`[![Dashcam - ${options.title || 'New Replay'}](https://replayable-api-production.herokuapp.com/replay/${replayId}/gif?shareKey=${shareKey})](${uploadResult.shareLink})`);
278
+ console.log('');
279
+ console.log(`Watch [Dashcam - ${options.title || 'New Replay'}](${uploadResult.shareLink}) on Dashcam`);
280
+ } else {
281
+ console.log(uploadResult.shareLink);
282
+ }
283
+ } catch (uploadError) {
284
+ console.error('Upload failed:', uploadError.message);
285
+ console.log('Recording saved locally:', result.outputPath);
286
+ }
287
+
288
+ process.exit(0);
289
+ } catch (error) {
290
+ logger.error('Error creating clip:', error);
291
+ console.error('Failed to create clip:', error.message);
292
+ process.exit(1);
293
+ }
294
+ });
295
+
296
+ // 'record' command - the main recording command with all options
297
+ program
298
+ .command('record')
299
+ .description('Start a recording terminal to be included in your dashcam video recording')
300
+ .option('-a, --audio', 'Include audio in the recording')
301
+ .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
302
+ .option('-o, --output <path>', 'Custom output path')
303
+ .option('-t, --title <title>', 'Title for the recording')
304
+ .option('-d, --description <description>', 'Description for the recording (supports markdown)')
305
+ .option('-p, --project <project>', 'Project ID to upload the recording to')
306
+ .option('-s, --silent', 'Silent mode - suppress all output')
307
+ .action(recordingAction);
308
+
309
+ program
310
+ .command('pipe')
311
+ .description('Pipe command output to dashcam to be included in recorded video')
312
+ .action(async () => {
313
+ try {
314
+ // Check if recording is active
315
+ if (!processManager.isRecordingActive()) {
316
+ console.error('No active recording. Start a recording first with "dashcam record" or "dashcam start"');
200
317
  process.exit(1);
201
318
  }
319
+
320
+ // Read from stdin
321
+ const chunks = [];
322
+ for await (const chunk of process.stdin) {
323
+ chunks.push(chunk);
324
+ // Also output to stdout so pipe continues to work
325
+ process.stdout.write(chunk);
326
+ }
327
+ const content = Buffer.concat(chunks).toString('utf-8');
328
+
329
+ // Import the log tracker to add the piped content
330
+ const { logsTrackerManager } = await import('../lib/logs/index.js');
331
+
332
+ // Add piped content as a log entry
333
+ logsTrackerManager.addPipedLog(content);
334
+
335
+ process.exit(0);
202
336
  } catch (error) {
203
- logger.error('Failed to start recording:', error);
204
- if (!options.silent) console.error('Failed to start recording:', error.message);
337
+ logger.error('Failed to pipe content:', error);
338
+ console.error('Failed to pipe content:', error.message);
205
339
  process.exit(1);
206
340
  }
207
341
  });
@@ -228,43 +362,94 @@ program
228
362
 
229
363
 
230
364
 
365
+ // 'start' command - alias for record with simple instant replay mode
366
+ program
367
+ .command('start')
368
+ .description('Start instant replay recording on dashcam')
369
+ .action(async () => {
370
+ // Call recordingAction with minimal options for instant replay
371
+ await recordingAction({
372
+ fps: '30',
373
+ audio: false,
374
+ silent: false
375
+ }, null);
376
+ });
377
+
231
378
  program
232
379
  .command('track')
233
- .description('Track logs from web URLs or application files')
234
- .option('--web <pattern>', 'Web URL pattern to track (can use wildcards like *)')
235
- .option('--app <pattern>', 'Application file pattern to track (can use wildcards like *)')
236
- .option('--name <name>', 'Name for the tracking configuration')
380
+ .description('Add a logs config to Dashcam')
381
+ .option('--name <name>', 'Name for the tracking configuration (required)')
382
+ .option('--type <type>', 'Type of tracker: "application" or "web" (required)')
383
+ .option('--pattern <pattern>', 'Pattern to track (can be used multiple times)', (value, previous) => {
384
+ return previous ? previous.concat([value]) : [value];
385
+ })
386
+ .option('--web <pattern>', 'Web URL pattern to track (can use wildcards like *) - deprecated, use --type=web --pattern instead')
387
+ .option('--app <pattern>', 'Application file pattern to track (can use wildcards like *) - deprecated, use --type=application --pattern instead')
237
388
  .action(async (options) => {
238
389
  try {
239
- // Validate that at least one pattern is provided
240
- if (!options.web && !options.app) {
241
- console.error('Error: Must provide either --web or --app pattern');
242
- process.exit(1);
243
- }
244
-
245
- if (options.web) {
246
- const config = {
247
- name: options.name || 'Web Pattern',
248
- type: 'web',
249
- patterns: [options.web],
250
- enabled: true
251
- };
390
+ // Support both old and new syntax
391
+ // New syntax: --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"
392
+ // Old syntax: --web <pattern> --app <pattern>
393
+
394
+ if (options.type && options.pattern) {
395
+ // New syntax validation
396
+ if (!options.name) {
397
+ console.error('Error: --name is required when using --type and --pattern');
398
+ console.log('Example: dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
399
+ process.exit(1);
400
+ }
401
+
402
+ if (options.type !== 'web' && options.type !== 'application') {
403
+ console.error('Error: --type must be either "web" or "application"');
404
+ process.exit(1);
405
+ }
252
406
 
253
- await createPattern(config);
254
- console.log('Web tracking pattern added successfully:', options.web);
255
- }
256
-
257
- if (options.app) {
258
407
  const config = {
259
- name: options.name || 'App Pattern',
260
- type: 'application',
261
- patterns: [options.app],
408
+ name: options.name,
409
+ type: options.type,
410
+ patterns: options.pattern,
262
411
  enabled: true
263
412
  };
264
413
 
265
414
  await createPattern(config);
266
- console.log('Application tracking pattern added successfully:', options.app);
415
+ console.log(`${options.type === 'web' ? 'Web' : 'Application'} tracking pattern added successfully:`, options.name);
416
+ console.log('Patterns:', options.pattern.join(', '));
417
+
418
+ } else if (options.web || options.app) {
419
+ // Old syntax for backward compatibility
420
+ if (options.web) {
421
+ const config = {
422
+ name: options.name || 'Web Pattern',
423
+ type: 'web',
424
+ patterns: [options.web],
425
+ enabled: true
426
+ };
427
+
428
+ await createPattern(config);
429
+ console.log('Web tracking pattern added successfully:', options.web);
430
+ }
431
+
432
+ if (options.app) {
433
+ const config = {
434
+ name: options.name || 'App Pattern',
435
+ type: 'application',
436
+ patterns: [options.app],
437
+ enabled: true
438
+ };
439
+
440
+ await createPattern(config);
441
+ console.log('Application tracking pattern added successfully:', options.app);
442
+ }
443
+ } else {
444
+ console.error('Error: Must provide either:');
445
+ console.log(' --name --type --pattern (new syntax)');
446
+ console.log(' --web or --app (old syntax)');
447
+ console.log('\nExamples:');
448
+ console.log(' dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
449
+ console.log(' dashcam track --web "*facebook.com*"');
450
+ process.exit(1);
267
451
  }
452
+
268
453
  process.exit(0);
269
454
  } catch (error) {
270
455
  console.error('Failed to add tracking pattern:', error.message);
@@ -566,4 +751,13 @@ program
566
751
  }
567
752
  });
568
753
 
754
+ // If no command specified and there are options like --md, treat as create command
755
+ program.action(async (options) => {
756
+ // Default to create command when running just "dashcam"
757
+ const createCommand = program.commands.find(cmd => cmd.name() === 'create');
758
+ if (createCommand && createCommand._actionHandler) {
759
+ await createCommand._actionHandler(options);
760
+ }
761
+ });
762
+
569
763
  program.parse();
package/lib/logs/index.js CHANGED
@@ -15,7 +15,13 @@ const CLI_CONFIG_FILE = path.join(process.cwd(), '.dashcam', 'cli-config.json');
15
15
 
16
16
  // Simple trim function for CLI (adapted from desktop app)
17
17
  async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipId) {
18
- logger.info('Trimming logs', { count: groupLogStatuses.length });
18
+ logger.info('Trimming logs', {
19
+ count: groupLogStatuses.length,
20
+ startMS,
21
+ endMS,
22
+ clientStartDate,
23
+ clientStartDateReadable: new Date(clientStartDate).toISOString()
24
+ });
19
25
 
20
26
  const REPLAY_DIR = path.join(os.tmpdir(), 'dashcam', 'recordings');
21
27
 
@@ -36,12 +42,24 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
36
42
  let events = content;
37
43
 
38
44
  // Convert events to relative time
39
- let relativeEvents = events.map((event) => {
45
+ let relativeEvents = events.map((event, index) => {
46
+ const originalTime = event.time;
40
47
  event.time = parseInt(event.time + '') - startMS;
41
48
  // Check if it's not already relative time
42
49
  if (event.time > 1_000_000_000_000) {
43
50
  // relative time = absolute time - clip start time
44
51
  event.time = event.time - clientStartDate;
52
+ if (index === 0) {
53
+ // Log first event for debugging
54
+ logger.info('First event timestamp conversion', {
55
+ originalTime,
56
+ clientStartDate,
57
+ relativeTime: event.time,
58
+ relativeSeconds: (event.time / 1000).toFixed(2),
59
+ startMS,
60
+ line: event.line ? event.line.substring(0, 50) : 'N/A'
61
+ });
62
+ }
45
63
  }
46
64
  return event;
47
65
  });
@@ -56,16 +74,19 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
56
74
  });
57
75
 
58
76
  if (status.type === 'cli') {
59
- // Remap logFile indices for CLI logs
60
- let map = {};
61
- filteredEvents = filteredEvents.map((event) => {
62
- let name = map[event.logFile] ?? Object.keys(map).length + 1;
63
- if (!map[event.logFile]) map[event.logFile] = name;
64
- return {
65
- ...event,
66
- logFile: name,
67
- };
77
+ // For CLI logs, keep the file paths in logFile field for UI display
78
+ // No need to remap to numeric indices
79
+
80
+ // Create items array showing unique files and their event counts
81
+ const fileCounts = {};
82
+ filteredEvents.forEach(event => {
83
+ fileCounts[event.logFile] = (fileCounts[event.logFile] || 0) + 1;
68
84
  });
85
+
86
+ status.items = Object.entries(fileCounts).map(([filePath, count]) => ({
87
+ item: filePath,
88
+ count
89
+ }));
69
90
  }
70
91
  } else if (status.type === 'web' && !webHandled) {
71
92
  logger.debug('Found web groupLog, handling all web groupLogs at once');
@@ -243,6 +264,41 @@ class LogsTrackerManager {
243
264
  this.removeCliTrackedPath(filePath);
244
265
  }
245
266
 
267
+ addPipedLog(content) {
268
+ // Find active recording instance
269
+ const activeInstances = Object.values(this.instances);
270
+ if (activeInstances.length === 0) {
271
+ logger.warn('No active recording to add piped content to');
272
+ return;
273
+ }
274
+
275
+ // Add to the most recent active instance (last one)
276
+ const instance = activeInstances[activeInstances.length - 1];
277
+ const timestamp = Date.now();
278
+
279
+ // Write to CLI tracker's temp file
280
+ if (instance.trackers && instance.trackers.cli) {
281
+ const pipedLog = {
282
+ time: timestamp,
283
+ logFile: 'piped-input',
284
+ content: content
285
+ };
286
+
287
+ // Write to the CLI tracker's output file
288
+ try {
289
+ const outputFile = path.join(instance.directory, 'cli-logs.jsonl');
290
+ const logLine = JSON.stringify(pipedLog) + '\n';
291
+ fs.appendFileSync(outputFile, logLine);
292
+ logger.info('Added piped content to recording', {
293
+ recorderId: instance.recorderId,
294
+ contentLength: content.length
295
+ });
296
+ } catch (error) {
297
+ logger.error('Failed to write piped content', { error });
298
+ }
299
+ }
300
+ }
301
+
246
302
  removeTracker(id) {
247
303
  // Try removing from web trackers first
248
304
  if (this.webLogsConfig[id]) {