@secondcontext/btx-cli 0.0.5 → 0.0.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.
@@ -365,6 +365,107 @@ function formatMeetingTranscript(m) {
365
365
  console.log(`[${time}] ${speaker}: ${seg.text}`);
366
366
  }
367
367
  }
368
+ function recordingTitle(recording) {
369
+ return (recording.meetingTitle?.trim() ||
370
+ recording.originalFilename?.trim() ||
371
+ 'Untitled recording');
372
+ }
373
+ function recordingTimestamp(recording) {
374
+ return recording.meetingDate || recording.uploadedAt || recording.transcribedAt || recording.createdAt || null;
375
+ }
376
+ function formatRecordingDuration(durationMs) {
377
+ if (!durationMs || durationMs <= 0)
378
+ return '';
379
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
380
+ const minutes = Math.floor(totalSeconds / 60);
381
+ const seconds = totalSeconds % 60;
382
+ const hours = Math.floor(minutes / 60);
383
+ if (hours > 0) {
384
+ return `${hours}:${String(minutes % 60).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
385
+ }
386
+ return `${minutes}:${String(seconds).padStart(2, '0')}`;
387
+ }
388
+ function formatRecordingTable(recordings) {
389
+ if (recordings.length === 0) {
390
+ console.log('No recordings found.');
391
+ return;
392
+ }
393
+ const idW = 10;
394
+ const dateW = 12;
395
+ const statusW = 12;
396
+ const sourceW = 12;
397
+ const durationW = 8;
398
+ console.log(`${padRight('ID', idW)} ${padRight('Date', dateW)} ${padRight('Status', statusW)} ${padRight('Source', sourceW)} ${padRight('Dur', durationW)} Title`);
399
+ console.log(`${'\u2500'.repeat(idW)} ${'\u2500'.repeat(dateW)} ${'\u2500'.repeat(statusW)} ${'\u2500'.repeat(sourceW)} ${'\u2500'.repeat(durationW)} ${'\u2500'.repeat(40)}`);
400
+ for (const recording of recordings) {
401
+ const date = recordingTimestamp(recording)
402
+ ? new Date(recordingTimestamp(recording)).toISOString().slice(0, 10)
403
+ : '';
404
+ console.log(`${padRight(truncate(recording.id, idW), idW)} ${padRight(date, dateW)} ${padRight(truncate(recording.status || '', statusW), statusW)} ${padRight(truncate(recording.source || '', sourceW), sourceW)} ${padRight(formatRecordingDuration(recording.durationMs), durationW)} ${truncate(recordingTitle(recording), 52)}`);
405
+ }
406
+ console.log(`\n${recordings.length} recording${recordings.length !== 1 ? 's' : ''} found`);
407
+ }
408
+ function formatRecordingDetail(recording) {
409
+ console.log(`Recording: ${recordingTitle(recording)}`);
410
+ console.log(`ID: ${recording.id}`);
411
+ if (recording.meetingId)
412
+ console.log(`Meeting ID: ${recording.meetingId}`);
413
+ if (recordingTimestamp(recording)) {
414
+ console.log(`Date: ${new Date(recordingTimestamp(recording)).toISOString().slice(0, 10)}`);
415
+ }
416
+ if (recording.source)
417
+ console.log(`Source: ${recording.source}`);
418
+ if (recording.status)
419
+ console.log(`Status: ${recording.status}`);
420
+ if (recording.kind)
421
+ console.log(`Kind: ${recording.kind}`);
422
+ if (recording.captureMode)
423
+ console.log(`Capture: ${recording.captureMode}`);
424
+ if (recording.languageHint) {
425
+ const detected = recording.detectedLanguage ? ` (detected ${recording.detectedLanguage})` : '';
426
+ console.log(`Language: ${recording.languageHint}${detected}`);
427
+ }
428
+ if (recording.durationMs)
429
+ console.log(`Duration: ${formatRecordingDuration(recording.durationMs)}`);
430
+ if (recording.originalFilename)
431
+ console.log(`Filename: ${recording.originalFilename}`);
432
+ if (recording.mimeType)
433
+ console.log(`MIME: ${recording.mimeType}`);
434
+ if (recording.error)
435
+ console.log(`Error: ${recording.error}`);
436
+ if (recording.transcriptText?.trim()) {
437
+ console.log(`\nTranscript Preview:\n ${recording.transcriptText.trim().replace(/\n/g, '\n ')}`);
438
+ }
439
+ }
440
+ function recordingSpeakerLabel(source) {
441
+ const normalized = source?.trim();
442
+ if (!normalized)
443
+ return 'Speaker';
444
+ return normalized.replace(/[_-]+/g, ' ');
445
+ }
446
+ function formatRecordingTranscript(recording) {
447
+ const transcript = recording.transcript ?? [];
448
+ const transcriptText = recording.transcriptText?.trim() ?? '';
449
+ if (transcript.length === 0 && !transcriptText) {
450
+ console.log('No transcript available for this recording.');
451
+ return;
452
+ }
453
+ console.log(`Transcript: ${recordingTitle(recording)}`);
454
+ if (recordingTimestamp(recording)) {
455
+ console.log(`Date: ${new Date(recordingTimestamp(recording)).toISOString().slice(0, 10)}`);
456
+ }
457
+ if (transcript.length > 0) {
458
+ console.log(`Segments: ${transcript.length}`);
459
+ console.log('');
460
+ for (const segment of transcript) {
461
+ const time = segment.start_time || '';
462
+ console.log(`[${time}] ${recordingSpeakerLabel(segment.speaker?.source)}: ${segment.text}`);
463
+ }
464
+ return;
465
+ }
466
+ console.log('');
467
+ console.log(transcriptText);
468
+ }
368
469
  function sessionPayload(s) {
369
470
  return JSON.stringify({
370
471
  sessionId: s.sessionId,
@@ -455,7 +556,7 @@ function formatSessionDetail(session, activities) {
455
556
  }
456
557
  // ── Help ─────────────────────────────────────────────────────────────────────
457
558
  const HELP = {
458
- top: `BTX CLI - manage tasks, contacts, meetings, and leads from a session.
559
+ top: `BTX CLI - manage tasks, contacts, meetings, recordings, and leads from a session.
459
560
 
460
561
  Usage:
461
562
  node "$BTX_CLI_PATH" <resource> <command> [flags]
@@ -467,6 +568,7 @@ Resources:
467
568
  contacts View and update contacts, add notes
468
569
  orgs View organizations, add notes
469
570
  meetings View meeting recordings and transcripts
571
+ recordings View recording imports and transcripts
470
572
  leads List lead types
471
573
  user Manage your profile notes
472
574
  intro-paths Find warm intro paths in your network
@@ -477,8 +579,10 @@ Resources:
477
579
  Examples:
478
580
  node "$BTX_CLI_PATH" tasks list --status todo
479
581
  node "$BTX_CLI_PATH" tasks list --json
582
+ node "$BTX_CLI_PATH" recordings list --days 7
583
+ node "$BTX_CLI_PATH" recordings transcript <recording-id>
480
584
  node "$BTX_CLI_PATH" meetings list --days 7
481
- node "$BTX_CLI_PATH" meetings get <meeting-id> --transcript
585
+ node "$BTX_CLI_PATH" meetings transcript <meeting-id>
482
586
  node "$BTX_CLI_PATH" contacts --help
483
587
 
484
588
  All resource commands support --json for machine-readable output.`,
@@ -652,6 +756,27 @@ Examples:
652
756
  node "$BTX_CLI_PATH" meetings list --query "onboarding"
653
757
  node "$BTX_CLI_PATH" meetings get abc123
654
758
  node "$BTX_CLI_PATH" meetings transcript abc123`,
759
+ recordings: `BTX recordings - view synced recordings and transcripts.
760
+
761
+ Commands:
762
+ list List recordings (filterable by date, source, status)
763
+ get Get recording details
764
+ transcript Get the full transcript for a recording
765
+
766
+ Flags:
767
+ recordings list [--days <n>] [--query "text"] [--status <status>] [--source <source>]
768
+ recordings get <recording-id>
769
+ recordings transcript <recording-id>
770
+
771
+ The --days flag filters to recordings from the last N days (default: all).
772
+ Use "list" to browse imported or uploaded recordings, "get" for metadata,
773
+ and "transcript" for the transcript text coding agents can consume directly.
774
+
775
+ Examples:
776
+ node "$BTX_CLI_PATH" recordings list
777
+ node "$BTX_CLI_PATH" recordings list --days 7
778
+ node "$BTX_CLI_PATH" recordings list --source granola
779
+ node "$BTX_CLI_PATH" recordings transcript abc123`,
655
780
  context: `BTX context - fetch the current project business context.
656
781
 
657
782
  Commands:
@@ -1284,7 +1409,7 @@ async function fetchSessions(flags) {
1284
1409
  return sessions;
1285
1410
  }
1286
1411
  async function fetchSession(sessionId) {
1287
- const result = await api('GET', `/sessions?sessionId=${encodeURIComponent(sessionId)}`);
1412
+ const result = await api('GET', `/sessions/${encodeURIComponent(sessionId)}`);
1288
1413
  return result.session ?? null;
1289
1414
  }
1290
1415
  async function fetchSessionActivities(sessionId, limit) {
@@ -1966,6 +2091,10 @@ async function fetchMeetings() {
1966
2091
  const result = await api('GET', '/meetings');
1967
2092
  return result?.meetings ?? [];
1968
2093
  }
2094
+ async function fetchRecordings() {
2095
+ const result = await api('GET', '/recordings');
2096
+ return result?.recordings ?? [];
2097
+ }
1969
2098
  async function meetingsList(flags) {
1970
2099
  let meetings = await fetchMeetings();
1971
2100
  if (flags.days) {
@@ -2012,6 +2141,71 @@ async function meetingsTranscript(id, flags) {
2012
2141
  }
2013
2142
  formatMeetingTranscript(meeting);
2014
2143
  }
2144
+ async function recordingsList(flags) {
2145
+ let recordings = await fetchRecordings();
2146
+ if (flags.days) {
2147
+ const cutoff = new Date();
2148
+ cutoff.setDate(cutoff.getDate() - Number(flags.days));
2149
+ const cutoffStr = cutoff.toISOString();
2150
+ recordings = recordings.filter((recording) => {
2151
+ const timestamp = recordingTimestamp(recording);
2152
+ return timestamp != null && timestamp >= cutoffStr;
2153
+ });
2154
+ }
2155
+ if (flags.status) {
2156
+ const expected = flags.status.toLowerCase();
2157
+ recordings = recordings.filter((recording) => (recording.status || '').toLowerCase() === expected);
2158
+ }
2159
+ if (flags.source) {
2160
+ const expected = flags.source.toLowerCase();
2161
+ recordings = recordings.filter((recording) => (recording.source || '').toLowerCase() === expected);
2162
+ }
2163
+ if (flags.query) {
2164
+ const q = flags.query.toLowerCase();
2165
+ recordings = recordings.filter((recording) => recordingTitle(recording).toLowerCase().includes(q) ||
2166
+ (recording.originalFilename || '').toLowerCase().includes(q) ||
2167
+ (recording.transcriptText || '').toLowerCase().includes(q) ||
2168
+ (recording.source || '').toLowerCase().includes(q) ||
2169
+ (recording.status || '').toLowerCase().includes(q));
2170
+ }
2171
+ if (isJson(flags)) {
2172
+ printJson(recordings);
2173
+ return;
2174
+ }
2175
+ formatRecordingTable(recordings);
2176
+ }
2177
+ async function recordingsGet(id, flags) {
2178
+ if (!id)
2179
+ die('Usage: btx recordings get <recording-id>');
2180
+ const recordings = await fetchRecordings();
2181
+ const recording = recordings.find((item) => item.id === id);
2182
+ if (!recording)
2183
+ die(`Recording not found: ${id}`);
2184
+ if (isJson(flags)) {
2185
+ printJson(recording);
2186
+ return;
2187
+ }
2188
+ formatRecordingDetail(recording);
2189
+ }
2190
+ async function recordingsTranscript(id, flags) {
2191
+ if (!id)
2192
+ die('Usage: btx recordings transcript <recording-id>');
2193
+ const recordings = await fetchRecordings();
2194
+ const recording = recordings.find((item) => item.id === id);
2195
+ if (!recording)
2196
+ die(`Recording not found: ${id}`);
2197
+ if (isJson(flags)) {
2198
+ printJson({
2199
+ id: recording.id,
2200
+ meetingId: recording.meetingId ?? null,
2201
+ title: recordingTitle(recording),
2202
+ transcriptText: recording.transcriptText ?? '',
2203
+ transcript: recording.transcript ?? [],
2204
+ });
2205
+ return;
2206
+ }
2207
+ formatRecordingTranscript(recording);
2208
+ }
2015
2209
  // ── Main ────────────────────────────────────────────────────────────────────
2016
2210
  export async function runRuntimeCli(args, env = process.env) {
2017
2211
  configureRuntime(env);
@@ -2032,6 +2226,7 @@ export async function runRuntimeCli(args, env = process.env) {
2032
2226
  'contacts',
2033
2227
  'orgs',
2034
2228
  'meetings',
2229
+ 'recordings',
2035
2230
  'leads',
2036
2231
  'user',
2037
2232
  'intro-paths',
@@ -2174,6 +2369,18 @@ export async function runRuntimeCli(args, env = process.env) {
2174
2369
  die(`Unknown meetings command: "${command || '(none)'}". Run: node "$BTX_CLI_PATH" meetings --help`);
2175
2370
  }
2176
2371
  }
2372
+ else if (resource === 'recordings') {
2373
+ switch (command) {
2374
+ case 'list':
2375
+ return recordingsList(flags);
2376
+ case 'get':
2377
+ return recordingsGet(positional[2], flags);
2378
+ case 'transcript':
2379
+ return recordingsTranscript(positional[2], flags);
2380
+ default:
2381
+ die(`Unknown recordings command: "${command || '(none)'}". Run: node "$BTX_CLI_PATH" recordings --help`);
2382
+ }
2383
+ }
2177
2384
  else if (resource === 'intro-paths') {
2178
2385
  switch (command) {
2179
2386
  case 'find':