@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.
- package/README.md +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1 -1
- package/dist/api.js.map +1 -1
- package/dist/bin.js +27 -26
- package/dist/bin.js.map +1 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +103 -21
- package/dist/login.js.map +1 -1
- package/dist/runtime-cli.d.ts.map +1 -1
- package/dist/runtime-cli.js +210 -3
- package/dist/runtime-cli.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime-cli.js
CHANGED
|
@@ -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
|
|
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
|
|
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':
|