fireflies-api 0.5.1 → 0.6.0
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/dist/cli/index.cjs +1435 -217
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1436 -222
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1372 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +462 -3
- package/dist/index.d.ts +462 -3
- package/dist/index.js +1357 -393
- package/dist/index.js.map +1 -1
- package/dist/middleware/express.cjs +459 -16
- package/dist/middleware/express.cjs.map +1 -1
- package/dist/middleware/express.d.cts +2 -2
- package/dist/middleware/express.d.ts +2 -2
- package/dist/middleware/express.js +459 -16
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/fastify.cjs +459 -16
- package/dist/middleware/fastify.cjs.map +1 -1
- package/dist/middleware/fastify.d.cts +2 -2
- package/dist/middleware/fastify.d.ts +2 -2
- package/dist/middleware/fastify.js +459 -16
- package/dist/middleware/fastify.js.map +1 -1
- package/dist/middleware/hono.cjs +459 -16
- package/dist/middleware/hono.cjs.map +1 -1
- package/dist/middleware/hono.d.cts +2 -2
- package/dist/middleware/hono.d.ts +2 -2
- package/dist/middleware/hono.js +459 -16
- package/dist/middleware/hono.js.map +1 -1
- package/dist/templates/digest/compact.md +8 -0
- package/dist/templates/digest/default.md +44 -0
- package/dist/templates/digest/executive.md +22 -0
- package/dist/{types-CaHcwnKw.d.ts → types-BMzVSd6w.d.ts} +1 -1
- package/dist/{types-BX-3JcRI.d.cts → types-BeXRmVD7.d.cts} +1 -1
- package/dist/{types-DIPZmUl3.d.ts → types-D2XsCR5R.d.ts} +120 -1
- package/dist/{types-C_XxdRd1.d.cts → types-zVGqyFzP.d.cts} +120 -1
- package/package.json +6 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -6,9 +6,14 @@ var path = require('path');
|
|
|
6
6
|
var url = require('url');
|
|
7
7
|
var commander = require('commander');
|
|
8
8
|
var socket_ioClient = require('socket.io-client');
|
|
9
|
+
var ora = require('ora');
|
|
9
10
|
var promises = require('fs/promises');
|
|
10
11
|
|
|
11
12
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
|
|
15
|
+
var ora__default = /*#__PURE__*/_interopDefault(ora);
|
|
16
|
+
|
|
12
17
|
// src/cli/index.ts
|
|
13
18
|
|
|
14
19
|
// src/errors.ts
|
|
@@ -285,8 +290,8 @@ async function retry(fn, options) {
|
|
|
285
290
|
if (attempt >= maxRetries || !shouldRetry(error, attempt)) {
|
|
286
291
|
throw error;
|
|
287
292
|
}
|
|
288
|
-
const
|
|
289
|
-
await sleep(
|
|
293
|
+
const delay2 = calculateDelay(error, attempt, baseDelay, maxDelay);
|
|
294
|
+
await sleep(delay2);
|
|
290
295
|
}
|
|
291
296
|
}
|
|
292
297
|
throw lastError;
|
|
@@ -435,9 +440,9 @@ var GraphQLClient = class {
|
|
|
435
440
|
if (!this.rateLimitTracker || !this.rateLimitConfig?.throttle) {
|
|
436
441
|
return;
|
|
437
442
|
}
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
440
|
-
await sleep2(
|
|
443
|
+
const delay2 = this.rateLimitTracker.getThrottleDelay(this.rateLimitConfig.throttle);
|
|
444
|
+
if (delay2 > 0) {
|
|
445
|
+
await sleep2(delay2);
|
|
441
446
|
}
|
|
442
447
|
}
|
|
443
448
|
/**
|
|
@@ -1235,6 +1240,46 @@ function getGroupKeyFn(groupBy2) {
|
|
|
1235
1240
|
}
|
|
1236
1241
|
}
|
|
1237
1242
|
|
|
1243
|
+
// src/helpers/batch.ts
|
|
1244
|
+
async function* batch(items, processor, options = {}) {
|
|
1245
|
+
const { delayMs = 100, handleRateLimit = true, maxRateLimitRetries = 3 } = options;
|
|
1246
|
+
let isFirst = true;
|
|
1247
|
+
for await (const item of items) {
|
|
1248
|
+
if (!isFirst && delayMs > 0) {
|
|
1249
|
+
await delay(delayMs);
|
|
1250
|
+
}
|
|
1251
|
+
isFirst = false;
|
|
1252
|
+
yield await processWithRetry(item, processor, {
|
|
1253
|
+
handleRateLimit,
|
|
1254
|
+
maxRateLimitRetries
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
async function processWithRetry(item, processor, options) {
|
|
1259
|
+
const { handleRateLimit, maxRateLimitRetries } = options;
|
|
1260
|
+
let retries = 0;
|
|
1261
|
+
while (true) {
|
|
1262
|
+
try {
|
|
1263
|
+
const result = await processor(item);
|
|
1264
|
+
return { item, result };
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
if (handleRateLimit && err instanceof RateLimitError && retries < maxRateLimitRetries) {
|
|
1267
|
+
const waitTime = err.retryAfter ?? 1e3;
|
|
1268
|
+
await delay(waitTime);
|
|
1269
|
+
retries++;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
return {
|
|
1273
|
+
item,
|
|
1274
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function delay(ms) {
|
|
1280
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1238
1283
|
// src/helpers/domain-utils.ts
|
|
1239
1284
|
function extractDomain(email) {
|
|
1240
1285
|
const atIndex = email.indexOf("@");
|
|
@@ -1250,6 +1295,332 @@ function hasExternalParticipants(participants, internalDomain) {
|
|
|
1250
1295
|
});
|
|
1251
1296
|
}
|
|
1252
1297
|
|
|
1298
|
+
// src/helpers/markdown.ts
|
|
1299
|
+
var DEFAULT_OPTIONS2 = {
|
|
1300
|
+
includeMetadata: true,
|
|
1301
|
+
includeSummary: true,
|
|
1302
|
+
includeActionItems: true,
|
|
1303
|
+
actionItemFormat: "checkbox",
|
|
1304
|
+
includeTimestamps: false,
|
|
1305
|
+
speakerFormat: "bold",
|
|
1306
|
+
groupBySpeaker: true
|
|
1307
|
+
};
|
|
1308
|
+
async function transcriptToMarkdown(transcript, options = {}) {
|
|
1309
|
+
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
1310
|
+
const sections = [];
|
|
1311
|
+
if (opts.includeMetadata) {
|
|
1312
|
+
sections.push(formatMetadata(transcript));
|
|
1313
|
+
}
|
|
1314
|
+
if (opts.includeSummary && transcript.summary) {
|
|
1315
|
+
sections.push(formatSummary(transcript.summary, opts));
|
|
1316
|
+
}
|
|
1317
|
+
if (transcript.sentences && transcript.sentences.length > 0) {
|
|
1318
|
+
sections.push(formatTranscript(transcript.sentences, opts));
|
|
1319
|
+
}
|
|
1320
|
+
const content = sections.join("\n\n---\n\n");
|
|
1321
|
+
await writeIfOutputPath(content, options.outputPath);
|
|
1322
|
+
return content;
|
|
1323
|
+
}
|
|
1324
|
+
function formatMetadata(transcript) {
|
|
1325
|
+
const lines = [`# ${transcript.title || "Untitled Meeting"}`];
|
|
1326
|
+
if (transcript.dateString) {
|
|
1327
|
+
lines.push(`
|
|
1328
|
+
**Date:** ${formatDate(transcript.dateString)}`);
|
|
1329
|
+
}
|
|
1330
|
+
const duration = calculateDuration(transcript);
|
|
1331
|
+
if (duration > 0) {
|
|
1332
|
+
lines.push(`**Duration:** ${formatDuration(duration)}`);
|
|
1333
|
+
}
|
|
1334
|
+
const participants = getParticipantNames(transcript);
|
|
1335
|
+
if (participants.length > 0) {
|
|
1336
|
+
lines.push(`**Participants:** ${participants.join(", ")}`);
|
|
1337
|
+
}
|
|
1338
|
+
return lines.join("\n");
|
|
1339
|
+
}
|
|
1340
|
+
function calculateDuration(transcript) {
|
|
1341
|
+
if (transcript.sentences && transcript.sentences.length > 0) {
|
|
1342
|
+
const lastSentence = transcript.sentences[transcript.sentences.length - 1];
|
|
1343
|
+
if (lastSentence) {
|
|
1344
|
+
return parseFloat(lastSentence.end_time);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return transcript.duration || 0;
|
|
1348
|
+
}
|
|
1349
|
+
function formatSummary(summary, opts) {
|
|
1350
|
+
const sections = ["## Summary"];
|
|
1351
|
+
if (summary.gist) {
|
|
1352
|
+
sections.push("", summary.gist);
|
|
1353
|
+
}
|
|
1354
|
+
if (summary.bullet_gist) {
|
|
1355
|
+
const bullets = parseMultilineField(summary.bullet_gist);
|
|
1356
|
+
if (bullets.length > 0) {
|
|
1357
|
+
sections.push("", "### Key Points");
|
|
1358
|
+
sections.push(bullets.map((p) => `- ${p}`).join("\n"));
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
if (opts.includeActionItems && summary.action_items) {
|
|
1362
|
+
const items = parseMultilineField(summary.action_items);
|
|
1363
|
+
if (items.length > 0) {
|
|
1364
|
+
sections.push("", "### Action Items");
|
|
1365
|
+
const prefix = opts.actionItemFormat === "checkbox" ? "- [ ] " : "- ";
|
|
1366
|
+
sections.push(items.map((a) => `${prefix}${a}`).join("\n"));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return sections.join("\n");
|
|
1370
|
+
}
|
|
1371
|
+
function formatTranscript(sentences, opts) {
|
|
1372
|
+
const lines = ["## Transcript"];
|
|
1373
|
+
if (opts.groupBySpeaker) {
|
|
1374
|
+
const groups = groupSentencesBySpeaker(sentences);
|
|
1375
|
+
for (const group of groups) {
|
|
1376
|
+
lines.push("", formatSpeakerGroup(group, opts));
|
|
1377
|
+
}
|
|
1378
|
+
} else {
|
|
1379
|
+
for (const sentence of sentences) {
|
|
1380
|
+
lines.push("", formatSentence(sentence, opts));
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return lines.join("\n");
|
|
1384
|
+
}
|
|
1385
|
+
function groupSentencesBySpeaker(sentences) {
|
|
1386
|
+
const groups = [];
|
|
1387
|
+
let current = null;
|
|
1388
|
+
for (const sentence of sentences) {
|
|
1389
|
+
if (!current || current.speakerName !== sentence.speaker_name) {
|
|
1390
|
+
current = { speakerName: sentence.speaker_name, sentences: [] };
|
|
1391
|
+
groups.push(current);
|
|
1392
|
+
}
|
|
1393
|
+
current.sentences.push(sentence);
|
|
1394
|
+
}
|
|
1395
|
+
return groups;
|
|
1396
|
+
}
|
|
1397
|
+
function formatSpeakerGroup(group, opts) {
|
|
1398
|
+
const speaker = formatSpeakerName(group.speakerName, opts.speakerFormat);
|
|
1399
|
+
const text = group.sentences.map((s) => s.text).join(" ");
|
|
1400
|
+
const firstSentence = group.sentences[0];
|
|
1401
|
+
if (opts.includeTimestamps && firstSentence) {
|
|
1402
|
+
const timestamp = formatTimestamp(firstSentence.start_time);
|
|
1403
|
+
return `${timestamp} ${speaker} ${text}`;
|
|
1404
|
+
}
|
|
1405
|
+
return `${speaker} ${text}`;
|
|
1406
|
+
}
|
|
1407
|
+
function formatSentence(sentence, opts) {
|
|
1408
|
+
const speaker = formatSpeakerName(sentence.speaker_name, opts.speakerFormat);
|
|
1409
|
+
if (opts.includeTimestamps) {
|
|
1410
|
+
const timestamp = formatTimestamp(sentence.start_time);
|
|
1411
|
+
return `${timestamp} ${speaker} ${sentence.text}`;
|
|
1412
|
+
}
|
|
1413
|
+
return `${speaker} ${sentence.text}`;
|
|
1414
|
+
}
|
|
1415
|
+
function formatSpeakerName(name, format) {
|
|
1416
|
+
switch (format) {
|
|
1417
|
+
case "bold":
|
|
1418
|
+
return `**${name}:**`;
|
|
1419
|
+
case "plain":
|
|
1420
|
+
return `${name}:`;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function formatTimestamp(startTime) {
|
|
1424
|
+
const seconds = parseFloat(startTime);
|
|
1425
|
+
const mins = Math.floor(seconds / 60);
|
|
1426
|
+
const secs = Math.floor(seconds % 60);
|
|
1427
|
+
return `[${mins}:${secs.toString().padStart(2, "0")}]`;
|
|
1428
|
+
}
|
|
1429
|
+
function formatDuration(seconds) {
|
|
1430
|
+
const hours = Math.floor(seconds / 3600);
|
|
1431
|
+
const mins = Math.floor(seconds % 3600 / 60);
|
|
1432
|
+
if (hours > 0) {
|
|
1433
|
+
return `${hours}h ${mins}m`;
|
|
1434
|
+
}
|
|
1435
|
+
return `${mins} minutes`;
|
|
1436
|
+
}
|
|
1437
|
+
function formatDate(isoString) {
|
|
1438
|
+
return new Date(isoString).toLocaleDateString("en-US", {
|
|
1439
|
+
weekday: "long",
|
|
1440
|
+
year: "numeric",
|
|
1441
|
+
month: "long",
|
|
1442
|
+
day: "numeric"
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
function getParticipantNames(transcript) {
|
|
1446
|
+
if (transcript.meeting_attendees?.length) {
|
|
1447
|
+
return transcript.meeting_attendees.map((a) => a.displayName || a.name || a.email).filter(Boolean);
|
|
1448
|
+
}
|
|
1449
|
+
return transcript.speakers?.map((s) => s.name) || [];
|
|
1450
|
+
}
|
|
1451
|
+
function parseMultilineField(value) {
|
|
1452
|
+
return value.split(/\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1453
|
+
}
|
|
1454
|
+
async function writeIfOutputPath(content, outputPath) {
|
|
1455
|
+
if (outputPath) {
|
|
1456
|
+
const { writeFile: writeFile3 } = await import('fs/promises');
|
|
1457
|
+
await writeFile3(outputPath, content, "utf-8");
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// src/helpers/export-formats.ts
|
|
1462
|
+
var DEFAULT_TEXT_OPTIONS = {
|
|
1463
|
+
includeTimestamps: false,
|
|
1464
|
+
includeMetadata: true
|
|
1465
|
+
};
|
|
1466
|
+
var DEFAULT_CSV_OPTIONS = {
|
|
1467
|
+
includeHeader: true,
|
|
1468
|
+
delimiter: ","
|
|
1469
|
+
};
|
|
1470
|
+
function transcriptToText(transcript, options = {}) {
|
|
1471
|
+
const opts = { ...DEFAULT_TEXT_OPTIONS, ...options };
|
|
1472
|
+
const sections = [];
|
|
1473
|
+
if (opts.includeMetadata) {
|
|
1474
|
+
sections.push(formatTextMetadata(transcript));
|
|
1475
|
+
}
|
|
1476
|
+
if (transcript.sentences && transcript.sentences.length > 0) {
|
|
1477
|
+
sections.push(formatTextTranscript(transcript.sentences, opts));
|
|
1478
|
+
}
|
|
1479
|
+
return sections.join("\n\n");
|
|
1480
|
+
}
|
|
1481
|
+
function transcriptToCsv(transcript, options = {}) {
|
|
1482
|
+
const opts = { ...DEFAULT_CSV_OPTIONS, ...options };
|
|
1483
|
+
const d = opts.delimiter;
|
|
1484
|
+
const lines = [];
|
|
1485
|
+
if (opts.includeHeader) {
|
|
1486
|
+
lines.push(`timestamp${d}speaker${d}text${d}is_question${d}is_task`);
|
|
1487
|
+
}
|
|
1488
|
+
for (const sentence of transcript.sentences) {
|
|
1489
|
+
const isQuestion = Boolean(sentence.ai_filters?.question);
|
|
1490
|
+
const isTask = Boolean(sentence.ai_filters?.task);
|
|
1491
|
+
const row = [
|
|
1492
|
+
sentence.start_time,
|
|
1493
|
+
escapeCsvField(sentence.speaker_name, d),
|
|
1494
|
+
escapeCsvField(sentence.text, d),
|
|
1495
|
+
String(isQuestion),
|
|
1496
|
+
String(isTask)
|
|
1497
|
+
];
|
|
1498
|
+
lines.push(row.join(d));
|
|
1499
|
+
}
|
|
1500
|
+
return lines.join("\n");
|
|
1501
|
+
}
|
|
1502
|
+
function sanitizeFilename(title) {
|
|
1503
|
+
if (!title.trim()) {
|
|
1504
|
+
return "untitled";
|
|
1505
|
+
}
|
|
1506
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 100);
|
|
1507
|
+
}
|
|
1508
|
+
function generateExportFilename(transcript, extension) {
|
|
1509
|
+
const sanitizedTitle = sanitizeFilename(transcript.title);
|
|
1510
|
+
let datePrefix = "";
|
|
1511
|
+
if (transcript.dateString) {
|
|
1512
|
+
try {
|
|
1513
|
+
const date = new Date(transcript.dateString);
|
|
1514
|
+
if (!Number.isNaN(date.getTime())) {
|
|
1515
|
+
datePrefix = `${date.toISOString().slice(0, 10)}-`;
|
|
1516
|
+
}
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return `${datePrefix}${sanitizedTitle}.${extension}`;
|
|
1521
|
+
}
|
|
1522
|
+
async function exportTranscript(transcript, format) {
|
|
1523
|
+
switch (format) {
|
|
1524
|
+
case "markdown":
|
|
1525
|
+
return transcriptToMarkdown(transcript);
|
|
1526
|
+
case "json":
|
|
1527
|
+
return JSON.stringify(transcript, null, 2);
|
|
1528
|
+
case "txt":
|
|
1529
|
+
return transcriptToText(transcript);
|
|
1530
|
+
case "csv":
|
|
1531
|
+
return transcriptToCsv(transcript);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
async function createZipArchive(files) {
|
|
1535
|
+
const archiver = await import('archiver');
|
|
1536
|
+
const { Writable } = await import('stream');
|
|
1537
|
+
return new Promise((resolve, reject) => {
|
|
1538
|
+
const chunks = [];
|
|
1539
|
+
const archive = archiver.default("zip", { zlib: { level: 9 } });
|
|
1540
|
+
const writable = new Writable({
|
|
1541
|
+
write(chunk, _encoding, callback) {
|
|
1542
|
+
chunks.push(chunk);
|
|
1543
|
+
callback();
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
writable.on("finish", () => {
|
|
1547
|
+
resolve(Buffer.concat(chunks));
|
|
1548
|
+
});
|
|
1549
|
+
archive.on("error", reject);
|
|
1550
|
+
archive.pipe(writable);
|
|
1551
|
+
for (const file of files) {
|
|
1552
|
+
archive.append(file.content, { name: file.filename });
|
|
1553
|
+
}
|
|
1554
|
+
archive.finalize();
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
function formatTextMetadata(transcript) {
|
|
1558
|
+
const lines = [];
|
|
1559
|
+
lines.push(transcript.title || "Untitled Meeting");
|
|
1560
|
+
if (transcript.dateString) {
|
|
1561
|
+
lines.push(`Date: ${formatDate2(transcript.dateString)}`);
|
|
1562
|
+
}
|
|
1563
|
+
const participants = getParticipantNames2(transcript);
|
|
1564
|
+
if (participants.length > 0) {
|
|
1565
|
+
lines.push(`Participants: ${participants.join(", ")}`);
|
|
1566
|
+
}
|
|
1567
|
+
return lines.join("\n");
|
|
1568
|
+
}
|
|
1569
|
+
function formatTextTranscript(sentences, opts) {
|
|
1570
|
+
const groups = groupSentencesBySpeaker2(sentences);
|
|
1571
|
+
const lines = [];
|
|
1572
|
+
for (const group of groups) {
|
|
1573
|
+
const text = group.sentences.map((s) => s.text).join(" ");
|
|
1574
|
+
const speaker = group.speakerName || "Unknown";
|
|
1575
|
+
const firstSentence = group.sentences[0];
|
|
1576
|
+
if (opts.includeTimestamps && firstSentence) {
|
|
1577
|
+
const timestamp = formatTimestamp2(firstSentence.start_time);
|
|
1578
|
+
lines.push(`${timestamp} ${speaker}: ${text}`);
|
|
1579
|
+
} else {
|
|
1580
|
+
lines.push(`${speaker}: ${text}`);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return lines.join("\n");
|
|
1584
|
+
}
|
|
1585
|
+
function groupSentencesBySpeaker2(sentences) {
|
|
1586
|
+
const groups = [];
|
|
1587
|
+
let current = null;
|
|
1588
|
+
for (const sentence of sentences) {
|
|
1589
|
+
if (!current || current.speakerName !== sentence.speaker_name) {
|
|
1590
|
+
current = { speakerName: sentence.speaker_name, sentences: [] };
|
|
1591
|
+
groups.push(current);
|
|
1592
|
+
}
|
|
1593
|
+
current.sentences.push(sentence);
|
|
1594
|
+
}
|
|
1595
|
+
return groups;
|
|
1596
|
+
}
|
|
1597
|
+
function formatTimestamp2(startTime) {
|
|
1598
|
+
const seconds = parseFloat(startTime);
|
|
1599
|
+
const mins = Math.floor(seconds / 60);
|
|
1600
|
+
const secs = Math.floor(seconds % 60);
|
|
1601
|
+
return `[${mins}:${secs.toString().padStart(2, "0")}]`;
|
|
1602
|
+
}
|
|
1603
|
+
function formatDate2(isoString) {
|
|
1604
|
+
return new Date(isoString).toLocaleDateString("en-US", {
|
|
1605
|
+
weekday: "long",
|
|
1606
|
+
year: "numeric",
|
|
1607
|
+
month: "long",
|
|
1608
|
+
day: "numeric"
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
function getParticipantNames2(transcript) {
|
|
1612
|
+
if (transcript.meeting_attendees?.length) {
|
|
1613
|
+
return transcript.meeting_attendees.map((a) => a.displayName || a.name || a.email).filter(Boolean);
|
|
1614
|
+
}
|
|
1615
|
+
return transcript.speakers?.map((s) => s.name) || [];
|
|
1616
|
+
}
|
|
1617
|
+
function escapeCsvField(field, delimiter) {
|
|
1618
|
+
if (field.includes('"') || field.includes(delimiter) || field.includes("\n")) {
|
|
1619
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
1620
|
+
}
|
|
1621
|
+
return field;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1253
1624
|
// src/helpers/meeting-insights.ts
|
|
1254
1625
|
function analyzeMeetings(transcripts, options = {}) {
|
|
1255
1626
|
const { speakers, groupBy: groupBy2, topSpeakersCount = 10, topParticipantsCount = 10 } = options;
|
|
@@ -1723,11 +2094,26 @@ var TRANSCRIPT_LIST_FIELDS = `
|
|
|
1723
2094
|
summary_status
|
|
1724
2095
|
}
|
|
1725
2096
|
`;
|
|
1726
|
-
function
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
2097
|
+
function buildListFields(params) {
|
|
2098
|
+
const includeSentences = params?.includeSentences === true;
|
|
2099
|
+
const includeSummary = params?.includeSummary === true;
|
|
2100
|
+
if (!includeSentences && !includeSummary) {
|
|
2101
|
+
return TRANSCRIPT_LIST_FIELDS;
|
|
2102
|
+
}
|
|
2103
|
+
let fields = TRANSCRIPT_BASE_FIELDS;
|
|
2104
|
+
if (includeSentences) {
|
|
2105
|
+
fields += SENTENCES_FIELDS;
|
|
2106
|
+
}
|
|
2107
|
+
if (includeSummary) {
|
|
2108
|
+
fields += SUMMARY_FIELDS;
|
|
2109
|
+
}
|
|
2110
|
+
return fields;
|
|
2111
|
+
}
|
|
2112
|
+
function createTranscriptsAPI(client) {
|
|
2113
|
+
return {
|
|
2114
|
+
async get(id, params) {
|
|
2115
|
+
const fields = buildTranscriptFields(params);
|
|
2116
|
+
const query = `
|
|
1731
2117
|
query GetTranscript($id: String!) {
|
|
1732
2118
|
transcript(id: $id) {
|
|
1733
2119
|
${fields}
|
|
@@ -1738,6 +2124,7 @@ function createTranscriptsAPI(client) {
|
|
|
1738
2124
|
return normalizeTranscript(data.transcript);
|
|
1739
2125
|
},
|
|
1740
2126
|
async list(params) {
|
|
2127
|
+
const fields = buildListFields(params);
|
|
1741
2128
|
const query = `
|
|
1742
2129
|
query ListTranscripts(
|
|
1743
2130
|
$keyword: String
|
|
@@ -1775,13 +2162,23 @@ function createTranscriptsAPI(client) {
|
|
|
1775
2162
|
participant_email: $participant_email
|
|
1776
2163
|
date: $date
|
|
1777
2164
|
) {
|
|
1778
|
-
${
|
|
2165
|
+
${fields}
|
|
1779
2166
|
}
|
|
1780
2167
|
}
|
|
1781
2168
|
`;
|
|
2169
|
+
let internalDomain;
|
|
2170
|
+
if (params?.external) {
|
|
2171
|
+
const userQuery = "query { user { email } }";
|
|
2172
|
+
const userData = await client.execute(userQuery);
|
|
2173
|
+
internalDomain = extractDomain(userData.user.email);
|
|
2174
|
+
}
|
|
1782
2175
|
const variables = buildListVariables(params);
|
|
1783
2176
|
const data = await client.execute(query, variables);
|
|
1784
|
-
|
|
2177
|
+
let results = data.transcripts.map(normalizeTranscript);
|
|
2178
|
+
if (internalDomain) {
|
|
2179
|
+
results = results.filter((t) => hasExternalParticipants(t.participants, internalDomain));
|
|
2180
|
+
}
|
|
2181
|
+
return results;
|
|
1785
2182
|
},
|
|
1786
2183
|
async getSummary(id) {
|
|
1787
2184
|
const query = `
|
|
@@ -1828,9 +2225,10 @@ function createTranscriptsAPI(client) {
|
|
|
1828
2225
|
} = params;
|
|
1829
2226
|
const transcripts = [];
|
|
1830
2227
|
for await (const t of this.listAll({
|
|
2228
|
+
...listParams,
|
|
1831
2229
|
keyword: query,
|
|
1832
2230
|
scope,
|
|
1833
|
-
|
|
2231
|
+
includeSentences: true
|
|
1834
2232
|
})) {
|
|
1835
2233
|
transcripts.push(t);
|
|
1836
2234
|
if (limit && transcripts.length >= limit) break;
|
|
@@ -1838,8 +2236,7 @@ function createTranscriptsAPI(client) {
|
|
|
1838
2236
|
const allMatches = [];
|
|
1839
2237
|
let transcriptsWithMatches = 0;
|
|
1840
2238
|
for (const t of transcripts) {
|
|
1841
|
-
const
|
|
1842
|
-
const matches = searchTranscript(full, {
|
|
2239
|
+
const matches = searchTranscript(t, {
|
|
1843
2240
|
query,
|
|
1844
2241
|
caseSensitive,
|
|
1845
2242
|
speakers,
|
|
@@ -1891,13 +2288,13 @@ function createTranscriptsAPI(client) {
|
|
|
1891
2288
|
organizers,
|
|
1892
2289
|
participants,
|
|
1893
2290
|
user_id,
|
|
1894
|
-
channel_id
|
|
2291
|
+
channel_id,
|
|
2292
|
+
includeSentences: true
|
|
1895
2293
|
})) {
|
|
1896
2294
|
if (internalDomain && !hasExternalParticipants(t.participants, internalDomain)) {
|
|
1897
2295
|
continue;
|
|
1898
2296
|
}
|
|
1899
|
-
|
|
1900
|
-
transcripts.push(full);
|
|
2297
|
+
transcripts.push(t);
|
|
1901
2298
|
if (limit && transcripts.length >= limit) break;
|
|
1902
2299
|
}
|
|
1903
2300
|
return analyzeMeetings(transcripts, {
|
|
@@ -1915,13 +2312,20 @@ function createTranscriptsAPI(client) {
|
|
|
1915
2312
|
toDate,
|
|
1916
2313
|
mine,
|
|
1917
2314
|
organizers,
|
|
1918
|
-
participants
|
|
2315
|
+
participants,
|
|
2316
|
+
includeSummary: true
|
|
1919
2317
|
})) {
|
|
1920
|
-
|
|
1921
|
-
transcripts.push(full);
|
|
2318
|
+
transcripts.push(t);
|
|
1922
2319
|
if (limit && transcripts.length >= limit) break;
|
|
1923
2320
|
}
|
|
1924
2321
|
return aggregateActionItems(transcripts, {}, filterOptions);
|
|
2322
|
+
},
|
|
2323
|
+
async bulkExport(params = {}) {
|
|
2324
|
+
const { format = "markdown", asZip = false, onProgress } = params;
|
|
2325
|
+
const transcriptIds = await collectTranscriptIds(client, this, params);
|
|
2326
|
+
const files = await convertTranscripts(this, transcriptIds, format, onProgress);
|
|
2327
|
+
const zip = asZip ? await createZipArchive(files) : void 0;
|
|
2328
|
+
return { files, zip, format, totalExported: files.length };
|
|
1925
2329
|
}
|
|
1926
2330
|
};
|
|
1927
2331
|
}
|
|
@@ -2000,6 +2404,50 @@ function buildListVariables(params) {
|
|
|
2000
2404
|
date: params.date
|
|
2001
2405
|
};
|
|
2002
2406
|
}
|
|
2407
|
+
async function collectTranscriptIds(client, api, params) {
|
|
2408
|
+
const { ids, fromDate, toDate, mine, organizers, participants, external, limit } = params;
|
|
2409
|
+
if (ids?.length) {
|
|
2410
|
+
return ids;
|
|
2411
|
+
}
|
|
2412
|
+
let internalDomain;
|
|
2413
|
+
if (external) {
|
|
2414
|
+
const userQuery = "query { user { email } }";
|
|
2415
|
+
const userData = await client.execute(userQuery);
|
|
2416
|
+
internalDomain = extractDomain(userData.user.email);
|
|
2417
|
+
}
|
|
2418
|
+
const result = [];
|
|
2419
|
+
for await (const t of api.listAll({ fromDate, toDate, mine, organizers, participants })) {
|
|
2420
|
+
if (internalDomain && !hasExternalParticipants(t.participants, internalDomain)) {
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
result.push(t.id);
|
|
2424
|
+
if (limit && result.length >= limit) break;
|
|
2425
|
+
}
|
|
2426
|
+
return result;
|
|
2427
|
+
}
|
|
2428
|
+
async function convertTranscripts(api, transcriptIds, format, onProgress) {
|
|
2429
|
+
const files = [];
|
|
2430
|
+
const extension = format === "markdown" ? "md" : format;
|
|
2431
|
+
let completed = 0;
|
|
2432
|
+
const total = transcriptIds.length;
|
|
2433
|
+
for await (const result of batch(
|
|
2434
|
+
transcriptIds,
|
|
2435
|
+
async (id) => {
|
|
2436
|
+
const transcript = await api.get(id);
|
|
2437
|
+
const content = await exportTranscript(transcript, format);
|
|
2438
|
+
const filename = generateExportFilename(transcript, extension);
|
|
2439
|
+
return { id, title: transcript.title, filename, content };
|
|
2440
|
+
},
|
|
2441
|
+
{ delayMs: 100 }
|
|
2442
|
+
)) {
|
|
2443
|
+
if (!result.error) {
|
|
2444
|
+
files.push(result.result);
|
|
2445
|
+
}
|
|
2446
|
+
completed++;
|
|
2447
|
+
onProgress?.(completed, total);
|
|
2448
|
+
}
|
|
2449
|
+
return files;
|
|
2450
|
+
}
|
|
2003
2451
|
|
|
2004
2452
|
// src/graphql/queries/users.ts
|
|
2005
2453
|
var USER_FIELDS = `
|
|
@@ -2461,6 +2909,10 @@ function getOutputFormat(cmd) {
|
|
|
2461
2909
|
const opts = cmd.optsWithGlobals();
|
|
2462
2910
|
return opts.output ?? "json";
|
|
2463
2911
|
}
|
|
2912
|
+
function isProgressEnabled(cmd) {
|
|
2913
|
+
const opts = cmd.optsWithGlobals();
|
|
2914
|
+
return opts.progress ?? false;
|
|
2915
|
+
}
|
|
2464
2916
|
|
|
2465
2917
|
// src/cli/utils/error.ts
|
|
2466
2918
|
function handleError(error) {
|
|
@@ -2695,7 +3147,7 @@ function registerAiAppsCommand(program2) {
|
|
|
2695
3147
|
}
|
|
2696
3148
|
|
|
2697
3149
|
// src/cli/utils/parse.ts
|
|
2698
|
-
function
|
|
3150
|
+
function formatDuration2(seconds) {
|
|
2699
3151
|
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
2700
3152
|
return "0s";
|
|
2701
3153
|
}
|
|
@@ -2848,170 +3300,815 @@ function registerBitesCommand(program2) {
|
|
|
2848
3300
|
);
|
|
2849
3301
|
}
|
|
2850
3302
|
|
|
2851
|
-
// src/helpers/
|
|
2852
|
-
var
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
actionItemFormat: "checkbox",
|
|
2857
|
-
includeTimestamps: false,
|
|
2858
|
-
speakerFormat: "bold",
|
|
2859
|
-
groupBySpeaker: true
|
|
2860
|
-
};
|
|
2861
|
-
async function transcriptToMarkdown(transcript, options = {}) {
|
|
2862
|
-
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
2863
|
-
const sections = [];
|
|
2864
|
-
if (opts.includeMetadata) {
|
|
2865
|
-
sections.push(formatMetadata(transcript));
|
|
3303
|
+
// src/helpers/digest.ts
|
|
3304
|
+
var DAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
3305
|
+
function calculateStats(transcripts) {
|
|
3306
|
+
if (transcripts.length === 0) {
|
|
3307
|
+
return emptyStats();
|
|
2866
3308
|
}
|
|
2867
|
-
|
|
2868
|
-
|
|
3309
|
+
const totalMinutes = sumDurations2(transcripts);
|
|
3310
|
+
const meetingsByDay = calculateMeetingsByDay(transcripts);
|
|
3311
|
+
const busiestDay = findBusiestDay(meetingsByDay);
|
|
3312
|
+
return {
|
|
3313
|
+
totalMeetings: transcripts.length,
|
|
3314
|
+
totalMinutes,
|
|
3315
|
+
averageDuration: totalMinutes / transcripts.length,
|
|
3316
|
+
busiestDay,
|
|
3317
|
+
meetingsByDay
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
function emptyStats() {
|
|
3321
|
+
return {
|
|
3322
|
+
totalMeetings: 0,
|
|
3323
|
+
totalMinutes: 0,
|
|
3324
|
+
averageDuration: 0,
|
|
3325
|
+
busiestDay: "",
|
|
3326
|
+
meetingsByDay: {}
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
function sumDurations2(transcripts) {
|
|
3330
|
+
return transcripts.reduce((sum, t) => sum + (t.duration ?? 0), 0);
|
|
3331
|
+
}
|
|
3332
|
+
function calculateMeetingsByDay(transcripts) {
|
|
3333
|
+
const counts = {};
|
|
3334
|
+
for (const t of transcripts) {
|
|
3335
|
+
const date = parseDate2(t.dateString);
|
|
3336
|
+
if (!date) continue;
|
|
3337
|
+
const dayName = DAY_NAMES[date.getUTCDay()];
|
|
3338
|
+
if (dayName) {
|
|
3339
|
+
counts[dayName] = (counts[dayName] ?? 0) + 1;
|
|
3340
|
+
}
|
|
2869
3341
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
3342
|
+
return counts;
|
|
3343
|
+
}
|
|
3344
|
+
function findBusiestDay(meetingsByDay) {
|
|
3345
|
+
let busiestDay = "";
|
|
3346
|
+
let maxCount = 0;
|
|
3347
|
+
for (const [day, count] of Object.entries(meetingsByDay)) {
|
|
3348
|
+
if (count > maxCount) {
|
|
3349
|
+
maxCount = count;
|
|
3350
|
+
busiestDay = day;
|
|
3351
|
+
}
|
|
2872
3352
|
}
|
|
2873
|
-
|
|
2874
|
-
await writeIfOutputPath(content, options.outputPath);
|
|
2875
|
-
return content;
|
|
3353
|
+
return busiestDay;
|
|
2876
3354
|
}
|
|
2877
|
-
function
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
3355
|
+
function parseDate2(dateString) {
|
|
3356
|
+
if (!dateString) return null;
|
|
3357
|
+
const date = new Date(dateString);
|
|
3358
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
3359
|
+
}
|
|
3360
|
+
var MAX_KEY_POINTS_PER_MEETING = 5;
|
|
3361
|
+
function extractHighlights(transcripts) {
|
|
3362
|
+
const highlights = [];
|
|
3363
|
+
for (const t of transcripts) {
|
|
3364
|
+
const overview = t.summary?.overview;
|
|
3365
|
+
if (!overview || overview.trim().length === 0) continue;
|
|
3366
|
+
const keyPoints = extractKeyPoints(overview);
|
|
3367
|
+
if (keyPoints.length === 0) continue;
|
|
3368
|
+
highlights.push({
|
|
3369
|
+
meetingId: t.id,
|
|
3370
|
+
meetingTitle: t.title,
|
|
3371
|
+
meetingDate: t.dateString,
|
|
3372
|
+
keyPoints,
|
|
3373
|
+
decisions: extractDecisions(t)
|
|
3374
|
+
});
|
|
2882
3375
|
}
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
3376
|
+
return highlights;
|
|
3377
|
+
}
|
|
3378
|
+
function extractKeyPoints(overview) {
|
|
3379
|
+
const sentences = overview.split(/(?<=[.!?])\s+/).map((s) => s.trim().replace(/[.!?]$/, "")).map((s) => s.replace(/^-\s*/, "")).filter((s) => s.length > 0);
|
|
3380
|
+
return sentences.slice(0, MAX_KEY_POINTS_PER_MEETING);
|
|
3381
|
+
}
|
|
3382
|
+
function extractDecisions(transcript) {
|
|
3383
|
+
const decisions = [];
|
|
3384
|
+
const overview = transcript.summary?.overview ?? "";
|
|
3385
|
+
const decisionPattern = /(?:^|[.!?]\s*)([^.!?]*(?:decided?|decision|agreed|approved)[^.!?]*)/gi;
|
|
3386
|
+
for (; ; ) {
|
|
3387
|
+
const match = decisionPattern.exec(overview);
|
|
3388
|
+
if (!match) break;
|
|
3389
|
+
if (match[1]) {
|
|
3390
|
+
decisions.push(match[1].trim());
|
|
3391
|
+
}
|
|
2886
3392
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3393
|
+
return decisions;
|
|
3394
|
+
}
|
|
3395
|
+
function buildNameLookup(transcripts) {
|
|
3396
|
+
const namesByEmail = /* @__PURE__ */ new Map();
|
|
3397
|
+
for (const t of transcripts) {
|
|
3398
|
+
for (const attendee of t.meeting_attendees ?? []) {
|
|
3399
|
+
if (!attendee.email) continue;
|
|
3400
|
+
const normalizedEmail = attendee.email.toLowerCase();
|
|
3401
|
+
const name = attendee.displayName || attendee.name;
|
|
3402
|
+
if (name && !namesByEmail.has(normalizedEmail)) {
|
|
3403
|
+
namesByEmail.set(normalizedEmail, name);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
2890
3406
|
}
|
|
2891
|
-
return
|
|
3407
|
+
return namesByEmail;
|
|
2892
3408
|
}
|
|
2893
|
-
function
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
3409
|
+
function countParticipation(transcripts, namesByEmail) {
|
|
3410
|
+
const stats = /* @__PURE__ */ new Map();
|
|
3411
|
+
for (const t of transcripts) {
|
|
3412
|
+
const seenInMeeting = /* @__PURE__ */ new Set();
|
|
3413
|
+
for (const email of t.participants ?? []) {
|
|
3414
|
+
const normalizedEmail = email.toLowerCase();
|
|
3415
|
+
if (seenInMeeting.has(normalizedEmail)) continue;
|
|
3416
|
+
seenInMeeting.add(normalizedEmail);
|
|
3417
|
+
const existing = stats.get(normalizedEmail) ?? {
|
|
3418
|
+
name: namesByEmail.get(normalizedEmail) || extractNameFromEmail(email),
|
|
3419
|
+
meetingCount: 0,
|
|
3420
|
+
totalMinutes: 0
|
|
3421
|
+
};
|
|
3422
|
+
existing.meetingCount++;
|
|
3423
|
+
existing.totalMinutes += t.duration ?? 0;
|
|
3424
|
+
stats.set(normalizedEmail, existing);
|
|
2898
3425
|
}
|
|
2899
3426
|
}
|
|
2900
|
-
return
|
|
3427
|
+
return stats;
|
|
2901
3428
|
}
|
|
2902
|
-
function
|
|
2903
|
-
const
|
|
2904
|
-
|
|
2905
|
-
|
|
3429
|
+
function aggregateParticipants2(transcripts) {
|
|
3430
|
+
const namesByEmail = buildNameLookup(transcripts);
|
|
3431
|
+
const stats = countParticipation(transcripts, namesByEmail);
|
|
3432
|
+
return Array.from(stats, ([email, data]) => ({
|
|
3433
|
+
email,
|
|
3434
|
+
name: data.name,
|
|
3435
|
+
meetingCount: data.meetingCount,
|
|
3436
|
+
totalMinutes: data.totalMinutes
|
|
3437
|
+
})).sort((a, b) => b.meetingCount - a.meetingCount);
|
|
3438
|
+
}
|
|
3439
|
+
function extractNameFromEmail(email) {
|
|
3440
|
+
const localPart = email.split("@")[0] ?? email;
|
|
3441
|
+
return localPart;
|
|
3442
|
+
}
|
|
3443
|
+
function buildParticipantInfoList(emails, attendees) {
|
|
3444
|
+
const attendeeMap = /* @__PURE__ */ new Map();
|
|
3445
|
+
for (const attendee of attendees) {
|
|
3446
|
+
if (attendee.email) {
|
|
3447
|
+
attendeeMap.set(attendee.email.toLowerCase(), attendee);
|
|
3448
|
+
}
|
|
2906
3449
|
}
|
|
2907
|
-
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3450
|
+
return emails.map((email) => {
|
|
3451
|
+
const normalizedEmail = email.toLowerCase();
|
|
3452
|
+
const attendee = attendeeMap.get(normalizedEmail);
|
|
3453
|
+
return {
|
|
3454
|
+
email: normalizedEmail,
|
|
3455
|
+
name: attendee?.displayName || attendee?.name || extractNameFromEmail(normalizedEmail)
|
|
3456
|
+
};
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
function aggregateActionItemsForDigest(transcripts) {
|
|
3460
|
+
if (transcripts.length === 0) {
|
|
3461
|
+
return emptyActionItems();
|
|
3462
|
+
}
|
|
3463
|
+
const byAssignee = {};
|
|
3464
|
+
const byMeeting = [];
|
|
3465
|
+
const unassigned = [];
|
|
3466
|
+
const withDueDates = [];
|
|
3467
|
+
let total = 0;
|
|
3468
|
+
for (const t of transcripts) {
|
|
3469
|
+
const result = extractActionItems(t);
|
|
3470
|
+
const meetingItems = [];
|
|
3471
|
+
for (const item of result.items) {
|
|
3472
|
+
const digestItem = {
|
|
3473
|
+
...item,
|
|
3474
|
+
transcriptId: t.id,
|
|
3475
|
+
transcriptTitle: t.title,
|
|
3476
|
+
transcriptDate: t.dateString
|
|
3477
|
+
};
|
|
3478
|
+
total++;
|
|
3479
|
+
meetingItems.push(digestItem);
|
|
3480
|
+
if (item.assignee) {
|
|
3481
|
+
const existing = byAssignee[item.assignee] ?? [];
|
|
3482
|
+
existing.push(digestItem);
|
|
3483
|
+
byAssignee[item.assignee] = existing;
|
|
3484
|
+
} else {
|
|
3485
|
+
unassigned.push(digestItem);
|
|
3486
|
+
}
|
|
3487
|
+
if (item.dueDate) {
|
|
3488
|
+
withDueDates.push(digestItem);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (meetingItems.length > 0) {
|
|
3492
|
+
byMeeting.push({
|
|
3493
|
+
id: t.id,
|
|
3494
|
+
title: t.title,
|
|
3495
|
+
date: t.dateString,
|
|
3496
|
+
duration: t.duration ?? 0,
|
|
3497
|
+
participants: buildParticipantInfoList(t.participants ?? [], t.meeting_attendees ?? []),
|
|
3498
|
+
items: meetingItems
|
|
3499
|
+
});
|
|
2912
3500
|
}
|
|
2913
3501
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
3502
|
+
return { total, byAssignee, byMeeting, unassigned, withDueDates };
|
|
3503
|
+
}
|
|
3504
|
+
function emptyActionItems() {
|
|
3505
|
+
return {
|
|
3506
|
+
total: 0,
|
|
3507
|
+
byAssignee: {},
|
|
3508
|
+
byMeeting: [],
|
|
3509
|
+
unassigned: [],
|
|
3510
|
+
withDueDates: []
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function buildDigest(transcripts, options = {}) {
|
|
3514
|
+
const { includeActionItems = true, includeHighlights = true, includeStats = true } = options;
|
|
3515
|
+
if (transcripts.length === 0) {
|
|
3516
|
+
return emptyDigest();
|
|
3517
|
+
}
|
|
3518
|
+
const stats = includeStats ? calculateStats(transcripts) : emptyStats();
|
|
3519
|
+
const actionItems = includeActionItems ? aggregateActionItemsForDigest(transcripts) : emptyActionItems();
|
|
3520
|
+
const highlights = includeHighlights ? extractHighlights(transcripts) : [];
|
|
3521
|
+
const participants = aggregateParticipants2(transcripts);
|
|
3522
|
+
const meetings = transcripts.map(toMeetingSummary);
|
|
3523
|
+
const period = calculatePeriod(transcripts);
|
|
3524
|
+
const totalDuration = sumDurations2(transcripts);
|
|
3525
|
+
return {
|
|
3526
|
+
period,
|
|
3527
|
+
totalMeetings: transcripts.length,
|
|
3528
|
+
totalDuration,
|
|
3529
|
+
stats,
|
|
3530
|
+
actionItems,
|
|
3531
|
+
highlights,
|
|
3532
|
+
participants,
|
|
3533
|
+
meetings
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
function emptyDigest() {
|
|
3537
|
+
return {
|
|
3538
|
+
period: { from: "", to: "" },
|
|
3539
|
+
totalMeetings: 0,
|
|
3540
|
+
totalDuration: 0,
|
|
3541
|
+
stats: emptyStats(),
|
|
3542
|
+
actionItems: emptyActionItems(),
|
|
3543
|
+
highlights: [],
|
|
3544
|
+
participants: [],
|
|
3545
|
+
meetings: []
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
function toMeetingSummary(transcript) {
|
|
3549
|
+
return {
|
|
3550
|
+
id: transcript.id,
|
|
3551
|
+
title: transcript.title,
|
|
3552
|
+
date: transcript.dateString,
|
|
3553
|
+
duration: transcript.duration ?? 0,
|
|
3554
|
+
participants: (transcript.participants ?? []).length
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3557
|
+
function calculatePeriod(transcripts) {
|
|
3558
|
+
let earliest = null;
|
|
3559
|
+
let latest = null;
|
|
3560
|
+
for (const t of transcripts) {
|
|
3561
|
+
const date = parseDate2(t.dateString);
|
|
3562
|
+
if (!date) continue;
|
|
3563
|
+
if (!earliest || date < earliest) {
|
|
3564
|
+
earliest = date;
|
|
3565
|
+
}
|
|
3566
|
+
if (!latest || date > latest) {
|
|
3567
|
+
latest = date;
|
|
2920
3568
|
}
|
|
2921
3569
|
}
|
|
2922
|
-
return
|
|
3570
|
+
return {
|
|
3571
|
+
from: earliest ? formatDateOnly2(earliest) : "",
|
|
3572
|
+
to: latest ? formatDateOnly2(latest) : ""
|
|
3573
|
+
};
|
|
2923
3574
|
}
|
|
2924
|
-
function
|
|
2925
|
-
const
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
3575
|
+
function formatDateOnly2(date) {
|
|
3576
|
+
const year = date.getUTCFullYear();
|
|
3577
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
3578
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
3579
|
+
return `${year}-${month}-${day}`;
|
|
3580
|
+
}
|
|
3581
|
+
var BUILT_IN_TEMPLATES = ["default", "compact", "executive"];
|
|
3582
|
+
function getTemplatesDir() {
|
|
3583
|
+
const currentDir = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
3584
|
+
return path.join(currentDir, "..", "templates", "digest");
|
|
3585
|
+
}
|
|
3586
|
+
function isBuiltInTemplate(name) {
|
|
3587
|
+
return BUILT_IN_TEMPLATES.includes(name);
|
|
3588
|
+
}
|
|
3589
|
+
function loadTemplate(templateOption) {
|
|
3590
|
+
const templateName = templateOption ?? "default";
|
|
3591
|
+
if (isBuiltInTemplate(templateName)) {
|
|
3592
|
+
const templatePath = path.join(getTemplatesDir(), `${templateName}.md`);
|
|
3593
|
+
try {
|
|
3594
|
+
return fs.readFileSync(templatePath, "utf-8");
|
|
3595
|
+
} catch {
|
|
3596
|
+
return getInlineTemplate(templateName);
|
|
2930
3597
|
}
|
|
2931
|
-
}
|
|
2932
|
-
|
|
2933
|
-
|
|
3598
|
+
}
|
|
3599
|
+
if (templateOption && (templateOption.startsWith("#") || templateOption.includes("{{"))) {
|
|
3600
|
+
return templateOption;
|
|
3601
|
+
}
|
|
3602
|
+
try {
|
|
3603
|
+
return fs.readFileSync(templateName, "utf-8");
|
|
3604
|
+
} catch {
|
|
3605
|
+
if (templateName.includes("/") || templateName.includes("\\") || templateName.endsWith(".md")) {
|
|
3606
|
+
throw new Error(`Template file not found: ${templateName}`);
|
|
2934
3607
|
}
|
|
3608
|
+
return templateName;
|
|
2935
3609
|
}
|
|
2936
|
-
return lines.join("\n");
|
|
2937
3610
|
}
|
|
2938
|
-
function
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
3611
|
+
function getInlineTemplate(name) {
|
|
3612
|
+
switch (name) {
|
|
3613
|
+
case "compact":
|
|
3614
|
+
return COMPACT_TEMPLATE;
|
|
3615
|
+
case "executive":
|
|
3616
|
+
return EXECUTIVE_TEMPLATE;
|
|
3617
|
+
default:
|
|
3618
|
+
return DEFAULT_TEMPLATE;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
function renderDigest(digest, options) {
|
|
3622
|
+
const template = loadTemplate(options?.template);
|
|
3623
|
+
return renderTemplate(template, digest);
|
|
3624
|
+
}
|
|
3625
|
+
function formatDurationHtml(minutes) {
|
|
3626
|
+
const hours = Math.floor(minutes / 60);
|
|
3627
|
+
const mins = minutes % 60;
|
|
3628
|
+
return `${hours}h ${mins}m`;
|
|
3629
|
+
}
|
|
3630
|
+
function escapeHtml(str) {
|
|
3631
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3632
|
+
}
|
|
3633
|
+
function renderActionItemsHtml(digest) {
|
|
3634
|
+
const allActionItems = digest.actionItems.byMeeting.flatMap((m) => m.items);
|
|
3635
|
+
return allActionItems.map(
|
|
3636
|
+
(item) => `
|
|
3637
|
+
<li class="action-item">
|
|
3638
|
+
<input type="checkbox" disabled />
|
|
3639
|
+
<span class="action-text">${escapeHtml(item.text)}</span>
|
|
3640
|
+
${item.assignee ? `<span class="assignee">(${escapeHtml(item.assignee)})</span>` : ""}
|
|
3641
|
+
${item.dueDate ? `<span class="due-date">due ${escapeHtml(item.dueDate)}</span>` : ""}
|
|
3642
|
+
</li>`
|
|
3643
|
+
).join("\n");
|
|
3644
|
+
}
|
|
3645
|
+
function renderMeetingsHtml(digest) {
|
|
3646
|
+
return digest.meetings.map(
|
|
3647
|
+
(m) => `
|
|
3648
|
+
<tr>
|
|
3649
|
+
<td>${escapeHtml(m.title)}</td>
|
|
3650
|
+
<td>${escapeHtml(m.date)}</td>
|
|
3651
|
+
<td>${formatDurationHtml(m.duration)}</td>
|
|
3652
|
+
<td>${m.participants}</td>
|
|
3653
|
+
</tr>`
|
|
3654
|
+
).join("\n");
|
|
3655
|
+
}
|
|
3656
|
+
function renderHighlightsHtml(digest) {
|
|
3657
|
+
return digest.highlights.map(
|
|
3658
|
+
(h) => `
|
|
3659
|
+
<div class="highlight">
|
|
3660
|
+
<h4>${escapeHtml(h.meetingTitle)} (${escapeHtml(h.meetingDate)})</h4>
|
|
3661
|
+
<ul>
|
|
3662
|
+
${h.keyPoints.map((p) => `<li>${escapeHtml(p)}</li>`).join("\n")}
|
|
3663
|
+
</ul>
|
|
3664
|
+
</div>`
|
|
3665
|
+
).join("\n");
|
|
3666
|
+
}
|
|
3667
|
+
function renderParticipantsHtml(digest) {
|
|
3668
|
+
return digest.participants.map(
|
|
3669
|
+
(p) => `
|
|
3670
|
+
<tr>
|
|
3671
|
+
<td>${escapeHtml(p.name || p.email)}</td>
|
|
3672
|
+
<td>${escapeHtml(p.email)}</td>
|
|
3673
|
+
<td>${p.meetingCount}</td>
|
|
3674
|
+
<td>${formatDurationHtml(p.totalMinutes)}</td>
|
|
3675
|
+
</tr>`
|
|
3676
|
+
).join("\n");
|
|
3677
|
+
}
|
|
3678
|
+
function renderDigestHtml(digest) {
|
|
3679
|
+
const actionItemsHtml = renderActionItemsHtml(digest);
|
|
3680
|
+
const meetingsHtml = renderMeetingsHtml(digest);
|
|
3681
|
+
const highlightsHtml = renderHighlightsHtml(digest);
|
|
3682
|
+
const participantsHtml = renderParticipantsHtml(digest);
|
|
3683
|
+
return `<!DOCTYPE html>
|
|
3684
|
+
<html lang="en">
|
|
3685
|
+
<head>
|
|
3686
|
+
<meta charset="UTF-8">
|
|
3687
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3688
|
+
<title>Weekly Meeting Digest</title>
|
|
3689
|
+
<style>
|
|
3690
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; }
|
|
3691
|
+
h1 { color: #333; border-bottom: 2px solid #4a90d9; padding-bottom: 10px; }
|
|
3692
|
+
h2 { color: #4a90d9; margin-top: 30px; }
|
|
3693
|
+
h3 { color: #666; }
|
|
3694
|
+
.overview { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; }
|
|
3695
|
+
.stat-card { background: #f5f5f5; padding: 20px; border-radius: 8px; text-align: center; }
|
|
3696
|
+
.stat-value { font-size: 2em; font-weight: bold; color: #4a90d9; }
|
|
3697
|
+
.stat-label { color: #666; }
|
|
3698
|
+
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
|
3699
|
+
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
3700
|
+
th { background: #f5f5f5; font-weight: 600; }
|
|
3701
|
+
.action-item { list-style: none; padding: 8px 0; border-bottom: 1px solid #eee; }
|
|
3702
|
+
.action-item input { margin-right: 10px; }
|
|
3703
|
+
.assignee { color: #4a90d9; margin-left: 10px; }
|
|
3704
|
+
.due-date { color: #e74c3c; margin-left: 10px; font-size: 0.9em; }
|
|
3705
|
+
.highlight { background: #f9f9f9; padding: 15px; border-radius: 8px; margin: 10px 0; }
|
|
3706
|
+
.highlight h4 { margin: 0 0 10px 0; color: #333; }
|
|
3707
|
+
.highlight ul { margin: 0; padding-left: 20px; }
|
|
3708
|
+
footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #999; font-size: 0.9em; }
|
|
3709
|
+
</style>
|
|
3710
|
+
</head>
|
|
3711
|
+
<body>
|
|
3712
|
+
<h1>Weekly Meeting Digest</h1>
|
|
3713
|
+
<p><strong>${escapeHtml(digest.period.from)}</strong> to <strong>${escapeHtml(digest.period.to)}</strong></p>
|
|
3714
|
+
|
|
3715
|
+
<div class="overview">
|
|
3716
|
+
<div class="stat-card">
|
|
3717
|
+
<div class="stat-value">${digest.totalMeetings}</div>
|
|
3718
|
+
<div class="stat-label">Meetings</div>
|
|
3719
|
+
</div>
|
|
3720
|
+
<div class="stat-card">
|
|
3721
|
+
<div class="stat-value">${formatDurationHtml(digest.totalDuration)}</div>
|
|
3722
|
+
<div class="stat-label">Total Time</div>
|
|
3723
|
+
</div>
|
|
3724
|
+
<div class="stat-card">
|
|
3725
|
+
<div class="stat-value">${digest.actionItems.total}</div>
|
|
3726
|
+
<div class="stat-label">Action Items</div>
|
|
3727
|
+
</div>
|
|
3728
|
+
</div>
|
|
3729
|
+
|
|
3730
|
+
<h2>Meeting Stats</h2>
|
|
3731
|
+
<p>Busiest day: <strong>${escapeHtml(digest.stats.busiestDay)}</strong></p>
|
|
3732
|
+
<p>Average duration: <strong>${formatDurationHtml(digest.stats.averageDuration)}</strong></p>
|
|
3733
|
+
|
|
3734
|
+
<h2>Meetings</h2>
|
|
3735
|
+
<table>
|
|
3736
|
+
<thead>
|
|
3737
|
+
<tr><th>Title</th><th>Date</th><th>Duration</th><th>Participants</th></tr>
|
|
3738
|
+
</thead>
|
|
3739
|
+
<tbody>
|
|
3740
|
+
${meetingsHtml}
|
|
3741
|
+
</tbody>
|
|
3742
|
+
</table>
|
|
3743
|
+
|
|
3744
|
+
${digest.actionItems.total > 0 ? `
|
|
3745
|
+
<h2>Action Items (${digest.actionItems.total})</h2>
|
|
3746
|
+
<ul style="padding-left: 0;">
|
|
3747
|
+
${actionItemsHtml}
|
|
3748
|
+
</ul>` : ""}
|
|
3749
|
+
|
|
3750
|
+
${digest.highlights.length > 0 ? `
|
|
3751
|
+
<h2>Highlights</h2>
|
|
3752
|
+
${highlightsHtml}` : ""}
|
|
3753
|
+
|
|
3754
|
+
<h2>Participants</h2>
|
|
3755
|
+
<table>
|
|
3756
|
+
<thead>
|
|
3757
|
+
<tr><th>Name</th><th>Email</th><th>Meetings</th><th>Time</th></tr>
|
|
3758
|
+
</thead>
|
|
3759
|
+
<tbody>
|
|
3760
|
+
${participantsHtml}
|
|
3761
|
+
</tbody>
|
|
3762
|
+
</table>
|
|
3763
|
+
|
|
3764
|
+
<footer>
|
|
3765
|
+
Generated with fireflies-api
|
|
3766
|
+
</footer>
|
|
3767
|
+
</body>
|
|
3768
|
+
</html>`;
|
|
3769
|
+
}
|
|
3770
|
+
var FILTERS = {
|
|
3771
|
+
duration: (value) => {
|
|
3772
|
+
const minutes = Math.round(Number(value) || 0);
|
|
3773
|
+
const hours = Math.floor(minutes / 60);
|
|
3774
|
+
const mins = minutes % 60;
|
|
3775
|
+
return `${hours}h ${mins}m`;
|
|
3776
|
+
},
|
|
3777
|
+
date: (value) => {
|
|
3778
|
+
const str = String(value || "");
|
|
3779
|
+
if (!str) return "";
|
|
3780
|
+
const date = new Date(str);
|
|
3781
|
+
if (Number.isNaN(date.getTime())) return str;
|
|
3782
|
+
return date.toLocaleDateString("en-US", {
|
|
3783
|
+
month: "short",
|
|
3784
|
+
day: "numeric",
|
|
3785
|
+
year: "numeric"
|
|
3786
|
+
});
|
|
3787
|
+
},
|
|
3788
|
+
join: (value) => {
|
|
3789
|
+
if (Array.isArray(value)) {
|
|
3790
|
+
return value.join(", ");
|
|
2945
3791
|
}
|
|
2946
|
-
|
|
3792
|
+
return String(value || "");
|
|
3793
|
+
},
|
|
3794
|
+
lowercase: (value) => String(value || "").toLowerCase(),
|
|
3795
|
+
uppercase: (value) => String(value || "").toUpperCase()
|
|
3796
|
+
};
|
|
3797
|
+
function renderTemplate(template, data) {
|
|
3798
|
+
if (!template) return "";
|
|
3799
|
+
let result = template;
|
|
3800
|
+
result = processSections(result, data);
|
|
3801
|
+
result = processVariables(result, data);
|
|
3802
|
+
return result;
|
|
3803
|
+
}
|
|
3804
|
+
function processSections(template, data) {
|
|
3805
|
+
const sectionPattern = /\{\{#(\w+(?:\.\w+)*)\}\}([\s\S]*?)\{\{\/\1\}\}/g;
|
|
3806
|
+
let result = template;
|
|
3807
|
+
let iterations = 0;
|
|
3808
|
+
const maxIterations = 100;
|
|
3809
|
+
for (; ; ) {
|
|
3810
|
+
const match = sectionPattern.exec(result);
|
|
3811
|
+
if (!match || iterations >= maxIterations) break;
|
|
3812
|
+
iterations++;
|
|
3813
|
+
const [fullMatch, path, content] = match;
|
|
3814
|
+
if (!path || content === void 0) continue;
|
|
3815
|
+
const value = getNestedValue(data, path);
|
|
3816
|
+
const replacement = renderSectionValue(value, content, data);
|
|
3817
|
+
result = result.slice(0, match.index) + replacement + result.slice(match.index + fullMatch.length);
|
|
3818
|
+
sectionPattern.lastIndex = 0;
|
|
2947
3819
|
}
|
|
2948
|
-
return
|
|
3820
|
+
return result;
|
|
2949
3821
|
}
|
|
2950
|
-
function
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const firstSentence = group.sentences[0];
|
|
2954
|
-
if (opts.includeTimestamps && firstSentence) {
|
|
2955
|
-
const timestamp = formatTimestamp(firstSentence.start_time);
|
|
2956
|
-
return `${timestamp} ${speaker} ${text}`;
|
|
3822
|
+
function renderSectionValue(value, content, data) {
|
|
3823
|
+
if (Array.isArray(value)) {
|
|
3824
|
+
return renderArraySection(value, content, data);
|
|
2957
3825
|
}
|
|
2958
|
-
|
|
3826
|
+
if (isIterableObject(value)) {
|
|
3827
|
+
return renderObjectSection(value, content, data);
|
|
3828
|
+
}
|
|
3829
|
+
if (isTruthy(value)) {
|
|
3830
|
+
const processed = processSections(content, data);
|
|
3831
|
+
return processVariables(processed, data);
|
|
3832
|
+
}
|
|
3833
|
+
return "";
|
|
2959
3834
|
}
|
|
2960
|
-
function
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
3835
|
+
function renderArraySection(items, content, data) {
|
|
3836
|
+
let result = "";
|
|
3837
|
+
for (const item of items) {
|
|
3838
|
+
if (typeof item === "object" && item !== null) {
|
|
3839
|
+
const itemData = { ...data, ...item };
|
|
3840
|
+
const processed = processSections(content, itemData);
|
|
3841
|
+
result += processVariables(processed, itemData);
|
|
3842
|
+
} else {
|
|
3843
|
+
const itemContent = content.replace(/\{\{\.\}\}/g, String(item));
|
|
3844
|
+
result += processVariables(itemContent, data);
|
|
3845
|
+
}
|
|
2965
3846
|
}
|
|
2966
|
-
return
|
|
3847
|
+
return result;
|
|
2967
3848
|
}
|
|
2968
|
-
function
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3849
|
+
function renderObjectSection(obj, content, data) {
|
|
3850
|
+
let result = "";
|
|
3851
|
+
for (const [key, itemValue] of Object.entries(obj)) {
|
|
3852
|
+
const itemData = buildObjectItemData(data, key, itemValue);
|
|
3853
|
+
const itemContent = content.replace(/\{\{\.\}\}/g, key);
|
|
3854
|
+
const processed = processSections(itemContent, itemData);
|
|
3855
|
+
result += processVariables(processed, itemData);
|
|
2974
3856
|
}
|
|
3857
|
+
return result;
|
|
2975
3858
|
}
|
|
2976
|
-
function
|
|
2977
|
-
const
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3859
|
+
function buildObjectItemData(data, key, itemValue) {
|
|
3860
|
+
const baseData = { ...data, _key: key, _value: itemValue };
|
|
3861
|
+
if (typeof itemValue === "object" && itemValue !== null && !Array.isArray(itemValue)) {
|
|
3862
|
+
return { ...baseData, ...itemValue };
|
|
3863
|
+
}
|
|
3864
|
+
return baseData;
|
|
2981
3865
|
}
|
|
2982
|
-
function
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3866
|
+
function isIterableObject(value) {
|
|
3867
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
3868
|
+
}
|
|
3869
|
+
function isTruthy(value) {
|
|
3870
|
+
if (value === null || value === void 0) return false;
|
|
3871
|
+
if (typeof value === "boolean") return value;
|
|
3872
|
+
if (typeof value === "string") return value.length > 0;
|
|
3873
|
+
if (typeof value === "number") return true;
|
|
3874
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
3875
|
+
return true;
|
|
3876
|
+
}
|
|
3877
|
+
function processVariables(template, data) {
|
|
3878
|
+
return template.replace(
|
|
3879
|
+
/\{\{([\w.]+)(?:\s*\|\s*(\w+))?\}\}/g,
|
|
3880
|
+
(_match, path, filterName) => {
|
|
3881
|
+
const value = getNestedValue(data, path);
|
|
3882
|
+
if (value === void 0 || value === null) {
|
|
3883
|
+
return "";
|
|
3884
|
+
}
|
|
3885
|
+
if (filterName && FILTERS[filterName]) {
|
|
3886
|
+
return FILTERS[filterName](value);
|
|
3887
|
+
}
|
|
3888
|
+
return String(value);
|
|
3889
|
+
}
|
|
3890
|
+
);
|
|
3891
|
+
}
|
|
3892
|
+
function getNestedValue(obj, path) {
|
|
3893
|
+
const parts = path.split(".");
|
|
3894
|
+
let current = obj;
|
|
3895
|
+
for (const part of parts) {
|
|
3896
|
+
if (current === null || current === void 0) {
|
|
3897
|
+
return void 0;
|
|
3898
|
+
}
|
|
3899
|
+
if (typeof current !== "object") {
|
|
3900
|
+
return void 0;
|
|
3901
|
+
}
|
|
3902
|
+
current = current[part];
|
|
2987
3903
|
}
|
|
2988
|
-
return
|
|
3904
|
+
return current;
|
|
2989
3905
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3906
|
+
var DEFAULT_TEMPLATE = `# Weekly Meeting Digest
|
|
3907
|
+
**{{period.from}} to {{period.to}}**
|
|
3908
|
+
|
|
3909
|
+
## Overview
|
|
3910
|
+
- **{{totalMeetings}}** meetings
|
|
3911
|
+
- **{{totalDuration | duration}}** total time
|
|
3912
|
+
- **{{actionItems.total}}** action items
|
|
3913
|
+
|
|
3914
|
+
## Meeting Stats
|
|
3915
|
+
{{#stats.meetingsByDay}}
|
|
3916
|
+
- {{.}}
|
|
3917
|
+
{{/stats.meetingsByDay}}
|
|
3918
|
+
|
|
3919
|
+
Busiest day: {{stats.busiestDay}}
|
|
3920
|
+
Average duration: {{stats.averageDuration}} minutes
|
|
3921
|
+
|
|
3922
|
+
## Action Items
|
|
3923
|
+
{{#actionItems.byAssignee}}
|
|
3924
|
+
### {{.}}
|
|
3925
|
+
{{/actionItems.byAssignee}}
|
|
3926
|
+
|
|
3927
|
+
{{#actionItems.unassigned}}
|
|
3928
|
+
### Unassigned
|
|
3929
|
+
- [ ] {{text}}
|
|
3930
|
+
{{/actionItems.unassigned}}
|
|
3931
|
+
|
|
3932
|
+
## Highlights
|
|
3933
|
+
{{#highlights}}
|
|
3934
|
+
### {{meetingTitle}} ({{meetingDate | date}})
|
|
3935
|
+
{{#keyPoints}}
|
|
3936
|
+
- {{.}}
|
|
3937
|
+
{{/keyPoints}}
|
|
3938
|
+
{{/highlights}}
|
|
3939
|
+
|
|
3940
|
+
## Participants
|
|
3941
|
+
{{#participants}}
|
|
3942
|
+
- {{email}} ({{meetingCount}} meetings, {{totalMinutes | duration}})
|
|
3943
|
+
{{/participants}}
|
|
3944
|
+
|
|
3945
|
+
---
|
|
3946
|
+
*Generated with fireflies-api*
|
|
3947
|
+
`;
|
|
3948
|
+
var COMPACT_TEMPLATE = `# Weekly Digest: {{period.from}} - {{period.to}}
|
|
3949
|
+
|
|
3950
|
+
**{{totalMeetings}}** meetings | **{{totalDuration | duration}}** | **{{actionItems.total}}** action items
|
|
3951
|
+
|
|
3952
|
+
{{#actionItems.unassigned}}
|
|
3953
|
+
## Action Items
|
|
3954
|
+
- [ ] {{text}}
|
|
3955
|
+
{{/actionItems.unassigned}}
|
|
3956
|
+
`;
|
|
3957
|
+
var EXECUTIVE_TEMPLATE = `# Executive Summary
|
|
3958
|
+
**Weekly Meeting Report: {{period.from}} to {{period.to}}**
|
|
3959
|
+
|
|
3960
|
+
## Key Metrics
|
|
3961
|
+
| Metric | Value |
|
|
3962
|
+
|--------|-------|
|
|
3963
|
+
| Total Meetings | {{totalMeetings}} |
|
|
3964
|
+
| Total Time | {{totalDuration | duration}} |
|
|
3965
|
+
| Action Items | {{actionItems.total}} |
|
|
3966
|
+
| Participants | {{participants.length}} |
|
|
3967
|
+
|
|
3968
|
+
## Top Highlights
|
|
3969
|
+
{{#highlights}}
|
|
3970
|
+
- **{{meetingTitle}}**: {{#keyPoints}}{{.}} {{/keyPoints}}
|
|
3971
|
+
{{/highlights}}
|
|
3972
|
+
|
|
3973
|
+
## Outstanding Action Items
|
|
3974
|
+
{{#actionItems.unassigned}}
|
|
3975
|
+
- {{text}}
|
|
3976
|
+
{{/actionItems.unassigned}}
|
|
3977
|
+
|
|
3978
|
+
---
|
|
3979
|
+
*Executive Summary generated with fireflies-api*
|
|
3980
|
+
`;
|
|
3981
|
+
|
|
3982
|
+
// src/cli/utils/date.ts
|
|
3983
|
+
function daysAgo(days) {
|
|
3984
|
+
const date = /* @__PURE__ */ new Date();
|
|
3985
|
+
date.setDate(date.getDate() - days);
|
|
3986
|
+
date.setHours(0, 0, 0, 0);
|
|
3987
|
+
return date.toISOString();
|
|
2997
3988
|
}
|
|
2998
|
-
function
|
|
2999
|
-
|
|
3000
|
-
|
|
3989
|
+
function startOfToday() {
|
|
3990
|
+
const date = /* @__PURE__ */ new Date();
|
|
3991
|
+
date.setHours(0, 0, 0, 0);
|
|
3992
|
+
return date.toISOString();
|
|
3993
|
+
}
|
|
3994
|
+
function resolveDateRange(opts) {
|
|
3995
|
+
if (opts.today) {
|
|
3996
|
+
return { fromDate: startOfToday() };
|
|
3001
3997
|
}
|
|
3002
|
-
|
|
3998
|
+
if (opts.yesterday) {
|
|
3999
|
+
return { fromDate: daysAgo(1), toDate: startOfToday() };
|
|
4000
|
+
}
|
|
4001
|
+
if (opts.lastWeek) {
|
|
4002
|
+
return { fromDate: daysAgo(7) };
|
|
4003
|
+
}
|
|
4004
|
+
if (opts.lastMonth) {
|
|
4005
|
+
return { fromDate: daysAgo(30) };
|
|
4006
|
+
}
|
|
4007
|
+
if (opts.days) {
|
|
4008
|
+
const numDays = Number.parseInt(opts.days, 10);
|
|
4009
|
+
if (!Number.isNaN(numDays) && numDays > 0) {
|
|
4010
|
+
return { fromDate: daysAgo(numDays) };
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
return { fromDate: opts.from, toDate: opts.to };
|
|
3003
4014
|
}
|
|
3004
|
-
function
|
|
3005
|
-
|
|
4015
|
+
function createProgress(opts) {
|
|
4016
|
+
if (!opts.enabled || !process.stderr.isTTY) {
|
|
4017
|
+
return null;
|
|
4018
|
+
}
|
|
4019
|
+
return ora__default.default({ text: opts.text, stream: process.stderr });
|
|
3006
4020
|
}
|
|
3007
|
-
async function
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
4021
|
+
async function withProgress(opts, task) {
|
|
4022
|
+
const spinner = createProgress(opts);
|
|
4023
|
+
spinner?.start();
|
|
4024
|
+
const update = (text) => {
|
|
4025
|
+
if (spinner) {
|
|
4026
|
+
spinner.text = text;
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
try {
|
|
4030
|
+
const result = await task(update);
|
|
4031
|
+
spinner?.succeed();
|
|
4032
|
+
return result;
|
|
4033
|
+
} catch (err) {
|
|
4034
|
+
spinner?.fail();
|
|
4035
|
+
throw err;
|
|
3011
4036
|
}
|
|
3012
4037
|
}
|
|
3013
4038
|
|
|
3014
|
-
// src/cli/commands/
|
|
4039
|
+
// src/cli/commands/digest.ts
|
|
4040
|
+
function buildDigestFromTranscripts(transcripts, opts) {
|
|
4041
|
+
return buildDigest(transcripts, {
|
|
4042
|
+
includeActionItems: opts.actionItems !== false && !opts.statsOnly,
|
|
4043
|
+
includeHighlights: opts.highlights !== false && !opts.statsOnly,
|
|
4044
|
+
includeStats: true
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
4047
|
+
function renderDigestOutput(digest, outputFormat, template) {
|
|
4048
|
+
if (outputFormat === "json") {
|
|
4049
|
+
return JSON.stringify(digest, null, 2);
|
|
4050
|
+
}
|
|
4051
|
+
if (outputFormat === "html") {
|
|
4052
|
+
return renderDigestHtml(digest);
|
|
4053
|
+
}
|
|
4054
|
+
return renderDigest(digest, { template });
|
|
4055
|
+
}
|
|
4056
|
+
function registerDigestCommand(program2) {
|
|
4057
|
+
program2.command("digest").description("Generate a weekly meeting digest").option("--from <date>", "From date (YYYY-MM-DD or ISO 8601)").option("--to <date>", "To date (YYYY-MM-DD or ISO 8601)").option("--today", "Meetings from today").option("--yesterday", "Meetings from yesterday").option("--last-week", "Meetings from last 7 days").option("--last-month", "Meetings from last 30 days").option("--days <n>", "Meetings from last N days").option("--mine", "Only my transcripts").option("--external", "Only meetings with external (non-company) participants").option("--limit <n>", "Max transcripts to include").option(
|
|
4058
|
+
"--template <name>",
|
|
4059
|
+
"Template: default, compact, executive, or path to .md file",
|
|
4060
|
+
"default"
|
|
4061
|
+
).option("-o, --output-file <path>", "Write digest to file").option("--format <format>", "Output format: markdown, html, json (default: markdown)").option("--no-action-items", "Exclude action items section").option("--no-highlights", "Exclude highlights section").option("--stats-only", "Only show meeting statistics").action(
|
|
4062
|
+
withErrorHandling(async (opts) => {
|
|
4063
|
+
const client = getClient(program2);
|
|
4064
|
+
const format = getOutputFormat(program2);
|
|
4065
|
+
const showProgress = isProgressEnabled(program2);
|
|
4066
|
+
const { fromDate, toDate } = resolveDateRange(opts);
|
|
4067
|
+
if (!fromDate && !toDate) {
|
|
4068
|
+
writeLine("Error: Please specify a date range (--from, --to, --last-week, etc.)");
|
|
4069
|
+
process.exitCode = 1;
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
const digestOutput = await withProgress(
|
|
4073
|
+
{ enabled: showProgress, text: "Generating digest..." },
|
|
4074
|
+
async (update) => {
|
|
4075
|
+
update("Fetching transcripts...");
|
|
4076
|
+
const transcripts = await client.transcripts.list({
|
|
4077
|
+
fromDate,
|
|
4078
|
+
toDate,
|
|
4079
|
+
mine: opts.mine,
|
|
4080
|
+
external: opts.external,
|
|
4081
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
4082
|
+
includeSummary: true
|
|
4083
|
+
});
|
|
4084
|
+
if (transcripts.length === 0) {
|
|
4085
|
+
return { digest: null, rendered: null, transcriptCount: 0 };
|
|
4086
|
+
}
|
|
4087
|
+
update(`Building digest from ${transcripts.length} transcripts...`);
|
|
4088
|
+
const digest = buildDigestFromTranscripts(transcripts, opts);
|
|
4089
|
+
update("Rendering output...");
|
|
4090
|
+
const outputFormat = opts.format || (format === "json" ? "json" : "markdown");
|
|
4091
|
+
const rendered = renderDigestOutput(digest, outputFormat, opts.template);
|
|
4092
|
+
return { digest, rendered, transcriptCount: transcripts.length };
|
|
4093
|
+
}
|
|
4094
|
+
);
|
|
4095
|
+
if (!digestOutput.digest || !digestOutput.rendered) {
|
|
4096
|
+
writeLine("No transcripts found for the specified date range.");
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
if (opts.outputFile) {
|
|
4100
|
+
fs.writeFileSync(opts.outputFile, digestOutput.rendered);
|
|
4101
|
+
writeLine(
|
|
4102
|
+
`\u2713 Digest written to ${opts.outputFile} (${digestOutput.digest.actionItems.total} action items, ${digestOutput.transcriptCount} meetings)`
|
|
4103
|
+
);
|
|
4104
|
+
} else if (format === "json") {
|
|
4105
|
+
output(digestOutput.digest, "json");
|
|
4106
|
+
} else {
|
|
4107
|
+
writeLine(digestOutput.rendered);
|
|
4108
|
+
}
|
|
4109
|
+
})
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
3015
4112
|
function registerExportCommand(program2) {
|
|
3016
4113
|
program2.command("export <transcript-id> [output-file]").description("Export transcript to markdown").option("--no-summary", "Exclude summary section").option("--no-timestamps", "Exclude timestamps").option("--format <format>", "Output format: markdown, json", "markdown").action(
|
|
3017
4114
|
withErrorHandling(async (transcriptId, outputFile, opts) => {
|
|
@@ -3040,39 +4137,137 @@ function registerExportCommand(program2) {
|
|
|
3040
4137
|
})
|
|
3041
4138
|
);
|
|
3042
4139
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
4140
|
+
function registerExportBulkCommand(program2) {
|
|
4141
|
+
program2.command("export-bulk").description("Export multiple transcripts to files").option("--from <date>", "From date (YYYY-MM-DD or ISO 8601)").option("--to <date>", "To date (YYYY-MM-DD or ISO 8601)").option("--today", "Meetings from today").option("--yesterday", "Meetings from yesterday").option("--last-week", "Meetings from last 7 days").option("--last-month", "Meetings from last 30 days").option("--days <n>", "Meetings from last N days").option("--ids <ids>", "Comma-separated transcript IDs").option("--mine", "Only my transcripts").option("--external", "Only meetings with external (non-company) participants").option("--limit <n>", "Max transcripts to export").requiredOption("-d, --dest <path>", "Output directory or .zip file").option(
|
|
4142
|
+
"--format <format>",
|
|
4143
|
+
"Export format: markdown, json, txt, csv (default: markdown)",
|
|
4144
|
+
"markdown"
|
|
4145
|
+
).option("--zip", "Package as zip archive").option("--dry-run", "Show what would be exported without writing files").action(
|
|
4146
|
+
withErrorHandling(async (opts) => {
|
|
4147
|
+
const client = getClient(program2);
|
|
4148
|
+
const showProgress = isProgressEnabled(program2);
|
|
4149
|
+
const exportOpts = parseExportOptions(opts);
|
|
4150
|
+
if (!exportOpts) return;
|
|
4151
|
+
if (opts.dryRun) {
|
|
4152
|
+
await runDryMode(client, exportOpts);
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
await runExport(client, exportOpts, showProgress);
|
|
4156
|
+
})
|
|
4157
|
+
);
|
|
3055
4158
|
}
|
|
3056
|
-
function
|
|
3057
|
-
|
|
3058
|
-
|
|
4159
|
+
function parseExportOptions(opts) {
|
|
4160
|
+
const { fromDate, toDate } = resolveDateRange(opts);
|
|
4161
|
+
const format = validateFormat(opts.format);
|
|
4162
|
+
const outputPath = opts.dest;
|
|
4163
|
+
const asZip = Boolean(opts.zip) || outputPath.endsWith(".zip");
|
|
4164
|
+
const ids = opts.ids ? opts.ids.split(",").map((id) => id.trim()) : void 0;
|
|
4165
|
+
if (!fromDate && !toDate && !ids) {
|
|
4166
|
+
writeLine("Error: Please specify a date range (--from, --to, --last-week, etc.) or --ids");
|
|
4167
|
+
process.exitCode = 1;
|
|
4168
|
+
return null;
|
|
3059
4169
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
4170
|
+
return {
|
|
4171
|
+
fromDate,
|
|
4172
|
+
toDate,
|
|
4173
|
+
ids,
|
|
4174
|
+
mine: opts.mine,
|
|
4175
|
+
external: opts.external,
|
|
4176
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
4177
|
+
format,
|
|
4178
|
+
asZip,
|
|
4179
|
+
outputPath
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
async function runExport(client, opts, showProgress) {
|
|
4183
|
+
const result = await withProgress(
|
|
4184
|
+
{ enabled: showProgress, text: "Exporting transcripts..." },
|
|
4185
|
+
async (update) => {
|
|
4186
|
+
return client.transcripts.bulkExport({
|
|
4187
|
+
fromDate: opts.fromDate,
|
|
4188
|
+
toDate: opts.toDate,
|
|
4189
|
+
ids: opts.ids,
|
|
4190
|
+
mine: opts.mine,
|
|
4191
|
+
external: opts.external,
|
|
4192
|
+
limit: opts.limit,
|
|
4193
|
+
format: opts.format,
|
|
4194
|
+
asZip: opts.asZip,
|
|
4195
|
+
onProgress: (completed, total) => {
|
|
4196
|
+
update(`Exporting transcripts... ${completed}/${total}`);
|
|
4197
|
+
}
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
);
|
|
4201
|
+
await writeExportResult(result, opts);
|
|
4202
|
+
}
|
|
4203
|
+
async function writeExportResult(result, opts) {
|
|
4204
|
+
if (result.totalExported === 0) {
|
|
4205
|
+
writeLine("No transcripts found matching the criteria.");
|
|
4206
|
+
return;
|
|
3062
4207
|
}
|
|
3063
|
-
if (opts.
|
|
3064
|
-
|
|
4208
|
+
if (opts.asZip && result.zip) {
|
|
4209
|
+
await promises.writeFile(opts.outputPath, result.zip);
|
|
4210
|
+
writeLine(`\u2713 Exported ${result.totalExported} transcripts to ${opts.outputPath}`);
|
|
4211
|
+
} else {
|
|
4212
|
+
await promises.mkdir(opts.outputPath, { recursive: true });
|
|
4213
|
+
for (const file of result.files) {
|
|
4214
|
+
await promises.writeFile(path.join(opts.outputPath, file.filename), file.content);
|
|
4215
|
+
}
|
|
4216
|
+
writeLine(`\u2713 Exported ${result.totalExported} transcripts to ${opts.outputPath}/`);
|
|
3065
4217
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
4218
|
+
}
|
|
4219
|
+
function validateFormat(format) {
|
|
4220
|
+
const validFormats = ["markdown", "json", "txt", "csv"];
|
|
4221
|
+
if (!validFormats.includes(format)) {
|
|
4222
|
+
throw new Error(`Invalid format "${format}". Valid formats: ${validFormats.join(", ")}`);
|
|
3068
4223
|
}
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
4224
|
+
return format;
|
|
4225
|
+
}
|
|
4226
|
+
async function runDryMode(client, params) {
|
|
4227
|
+
writeLine("Dry run - no files will be written\n");
|
|
4228
|
+
const transcripts = await collectDryRunTranscripts(client, params);
|
|
4229
|
+
printDryRunResults(transcripts, params);
|
|
4230
|
+
}
|
|
4231
|
+
async function collectDryRunTranscripts(client, params) {
|
|
4232
|
+
const transcripts = [];
|
|
4233
|
+
if (params.ids?.length) {
|
|
4234
|
+
for (const id of params.ids) {
|
|
4235
|
+
try {
|
|
4236
|
+
const t = await client.transcripts.get(id, {
|
|
4237
|
+
includeSentences: false,
|
|
4238
|
+
includeSummary: false
|
|
4239
|
+
});
|
|
4240
|
+
transcripts.push({ id: t.id, title: t.title, date: t.dateString || "Unknown" });
|
|
4241
|
+
} catch {
|
|
4242
|
+
writeLine(` \u26A0 Transcript ${id} not found`);
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
} else {
|
|
4246
|
+
for await (const t of client.transcripts.listAll({
|
|
4247
|
+
fromDate: params.fromDate,
|
|
4248
|
+
toDate: params.toDate,
|
|
4249
|
+
mine: params.mine
|
|
4250
|
+
})) {
|
|
4251
|
+
transcripts.push({ id: t.id, title: t.title, date: t.dateString || "Unknown" });
|
|
4252
|
+
if (params.limit && transcripts.length >= params.limit) break;
|
|
3073
4253
|
}
|
|
3074
4254
|
}
|
|
3075
|
-
return
|
|
4255
|
+
return transcripts;
|
|
4256
|
+
}
|
|
4257
|
+
function printDryRunResults(transcripts, params) {
|
|
4258
|
+
if (transcripts.length === 0) {
|
|
4259
|
+
writeLine("No transcripts found matching the criteria.");
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
writeLine(`Would export ${transcripts.length} transcripts:
|
|
4263
|
+
`);
|
|
4264
|
+
for (const t of transcripts) {
|
|
4265
|
+
const dateStr = t.date !== "Unknown" ? new Date(t.date).toLocaleDateString() : "Unknown";
|
|
4266
|
+
writeLine(` ${dateStr} - ${t.title}`);
|
|
4267
|
+
}
|
|
4268
|
+
writeLine("");
|
|
4269
|
+
writeLine(`Format: ${params.format}`);
|
|
4270
|
+
writeLine(`Output: ${params.outputPath}${params.asZip ? "" : "/"}`);
|
|
3076
4271
|
}
|
|
3077
4272
|
|
|
3078
4273
|
// src/cli/commands/insights.ts
|
|
@@ -3084,9 +4279,11 @@ function registerInsightsCommand(program2) {
|
|
|
3084
4279
|
withErrorHandling(async (opts) => {
|
|
3085
4280
|
const client = getClient(program2);
|
|
3086
4281
|
const format = getOutputFormat(program2);
|
|
4282
|
+
const showProgress = isProgressEnabled(program2);
|
|
3087
4283
|
const { fromDate, toDate } = resolveDateRange(opts);
|
|
3088
|
-
const insights = await
|
|
3089
|
-
|
|
4284
|
+
const insights = await withProgress(
|
|
4285
|
+
{ enabled: showProgress, text: "Analyzing meetings..." },
|
|
4286
|
+
async () => client.transcripts.insights(buildInsightsParams(opts, fromDate, toDate))
|
|
3090
4287
|
);
|
|
3091
4288
|
outputInsights(insights, format);
|
|
3092
4289
|
})
|
|
@@ -3137,7 +4334,7 @@ function outputHeader(insights) {
|
|
|
3137
4334
|
}
|
|
3138
4335
|
function outputSummaryStats(insights) {
|
|
3139
4336
|
writeLine(`Total meetings: ${insights.totalMeetings}`);
|
|
3140
|
-
writeLine(`Total duration: ${
|
|
4337
|
+
writeLine(`Total duration: ${formatDuration2(insights.totalDurationMinutes * 60)}`);
|
|
3141
4338
|
writeLine(`Average duration: ${Math.round(insights.averageDurationMinutes)} min`);
|
|
3142
4339
|
writeLine("");
|
|
3143
4340
|
}
|
|
@@ -3146,7 +4343,7 @@ function outputDayOfWeekStats(byDayOfWeek) {
|
|
|
3146
4343
|
const sortedDays = getSortedDays(byDayOfWeek);
|
|
3147
4344
|
for (const { day, stats } of sortedDays) {
|
|
3148
4345
|
if (stats.count > 0) {
|
|
3149
|
-
const duration =
|
|
4346
|
+
const duration = formatDuration2(stats.totalMinutes * 60);
|
|
3150
4347
|
writeLine(` ${capitalize(day)}: ${stats.count} meetings (${duration})`);
|
|
3151
4348
|
}
|
|
3152
4349
|
}
|
|
@@ -3156,7 +4353,7 @@ function outputTimeGroupStats(byTimeGroup) {
|
|
|
3156
4353
|
if (!byTimeGroup || byTimeGroup.length === 0) return;
|
|
3157
4354
|
writeLine("By Period:");
|
|
3158
4355
|
for (const group of byTimeGroup) {
|
|
3159
|
-
const duration =
|
|
4356
|
+
const duration = formatDuration2(group.totalMinutes * 60);
|
|
3160
4357
|
const avg = Math.round(group.averageMinutes);
|
|
3161
4358
|
writeLine(` ${group.period}: ${group.count} meetings (${duration}, avg ${avg} min)`);
|
|
3162
4359
|
}
|
|
@@ -3192,7 +4389,7 @@ function outputTopSpeakers(speakers) {
|
|
|
3192
4389
|
for (let i = 0; i < speakers.length; i++) {
|
|
3193
4390
|
const s = speakers[i];
|
|
3194
4391
|
if (s) {
|
|
3195
|
-
const talkTime =
|
|
4392
|
+
const talkTime = formatDuration2(s.totalTalkTimeSeconds);
|
|
3196
4393
|
writeLine(` ${i + 1}. ${s.name} (${s.meetingCount} meetings, ${talkTime} talk time)`);
|
|
3197
4394
|
}
|
|
3198
4395
|
}
|
|
@@ -3200,7 +4397,7 @@ function outputTopSpeakers(speakers) {
|
|
|
3200
4397
|
function outputInsightsTable(insights) {
|
|
3201
4398
|
const summary = {
|
|
3202
4399
|
totalMeetings: insights.totalMeetings,
|
|
3203
|
-
totalDuration:
|
|
4400
|
+
totalDuration: formatDuration2(insights.totalDurationMinutes * 60),
|
|
3204
4401
|
avgDuration: `${Math.round(insights.averageDurationMinutes)} min`,
|
|
3205
4402
|
dateRange: `${insights.earliestMeeting} to ${insights.latestMeeting}`,
|
|
3206
4403
|
uniqueParticipants: insights.totalUniqueParticipants,
|
|
@@ -3375,21 +4572,25 @@ function registerSearchCommand(program2) {
|
|
|
3375
4572
|
withErrorHandling(async (query, opts) => {
|
|
3376
4573
|
const client = getClient(program2);
|
|
3377
4574
|
const format = getOutputFormat(program2);
|
|
4575
|
+
const showProgress = isProgressEnabled(program2);
|
|
3378
4576
|
const { fromDate, toDate } = resolveDateRange(opts);
|
|
3379
|
-
const results = await
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
4577
|
+
const results = await withProgress(
|
|
4578
|
+
{ enabled: showProgress, text: `Searching for "${query}"...` },
|
|
4579
|
+
async () => client.transcripts.search(query, {
|
|
4580
|
+
caseSensitive: opts.caseSensitive,
|
|
4581
|
+
scope: opts.scope,
|
|
4582
|
+
speakers: opts.speaker.length > 0 ? opts.speaker : void 0,
|
|
4583
|
+
filterQuestions: opts.questions,
|
|
4584
|
+
filterTasks: opts.tasks,
|
|
4585
|
+
contextLines: Number.parseInt(opts.context, 10),
|
|
4586
|
+
fromDate,
|
|
4587
|
+
toDate,
|
|
4588
|
+
mine: opts.mine,
|
|
4589
|
+
organizers: opts.organizer.length > 0 ? opts.organizer : void 0,
|
|
4590
|
+
participants: opts.participant.length > 0 ? opts.participant : void 0,
|
|
4591
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0
|
|
4592
|
+
})
|
|
4593
|
+
);
|
|
3393
4594
|
outputSearchResults(results, format);
|
|
3394
4595
|
})
|
|
3395
4596
|
);
|
|
@@ -3704,28 +4905,35 @@ function arrayOrUndefined(arr) {
|
|
|
3704
4905
|
}
|
|
3705
4906
|
function registerTranscriptsCommand(program2) {
|
|
3706
4907
|
const cmd = program2.command("transcripts").description("Manage transcripts");
|
|
3707
|
-
cmd.command("list").description("List transcripts").option("--limit <n>", "Max results (default: 20)", "20").option("--from <date>", "From date (YYYY-MM-DD or ISO 8601)").option("--to <date>", "To date (YYYY-MM-DD or ISO 8601)").option("--today", "Transcripts from today").option("--yesterday", "Transcripts from yesterday").option("--last-week", "Transcripts from last 7 days").option("--last-month", "Transcripts from last 30 days").option("--days <n>", "Transcripts from last N days").option("--mine", "Only my transcripts").option("--keyword <text>", "Search keyword").option("--scope <scope>", "Search scope: title, sentences, all (default: all)").option("--organizer <email>", "Filter by organizer email (repeatable)", collect3, []).option("--participant <email>", "Filter by participant email (repeatable)", collect3, []).option("--participant-me", "Only meetings where I am a participant").option("--user-id <id>", "Filter by user ID").option("--channel <id>", "Filter by channel ID").option("--normalize", "Output in normalized provider-agnostic format").action(
|
|
4908
|
+
cmd.command("list").description("List transcripts").option("--limit <n>", "Max results (default: 20)", "20").option("--from <date>", "From date (YYYY-MM-DD or ISO 8601)").option("--to <date>", "To date (YYYY-MM-DD or ISO 8601)").option("--today", "Transcripts from today").option("--yesterday", "Transcripts from yesterday").option("--last-week", "Transcripts from last 7 days").option("--last-month", "Transcripts from last 30 days").option("--days <n>", "Transcripts from last N days").option("--mine", "Only my transcripts").option("--keyword <text>", "Search keyword").option("--scope <scope>", "Search scope: title, sentences, all (default: all)").option("--organizer <email>", "Filter by organizer email (repeatable)", collect3, []).option("--participant <email>", "Filter by participant email (repeatable)", collect3, []).option("--participant-me", "Only meetings where I am a participant").option("--user-id <id>", "Filter by user ID").option("--channel <id>", "Filter by channel ID").option("--external", "Only meetings with external (non-company) participants").option("--include-sentences", "Include full transcript sentences (large response)").option("--include-summary", "Include summary with action items, keywords, etc.").option("--normalize", "Output in normalized provider-agnostic format").action(
|
|
3708
4909
|
withErrorHandling(async (opts) => {
|
|
3709
4910
|
const client = getClient(program2);
|
|
3710
4911
|
const format = getOutputFormat(program2);
|
|
4912
|
+
const showProgress = isProgressEnabled(program2);
|
|
3711
4913
|
const { fromDate, toDate } = resolveDateRange(opts);
|
|
3712
4914
|
const participants = [...opts.participant];
|
|
3713
4915
|
if (opts.participantMe) {
|
|
3714
4916
|
const me = await client.users.me();
|
|
3715
4917
|
participants.push(me.email);
|
|
3716
4918
|
}
|
|
3717
|
-
const transcripts = await
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4919
|
+
const transcripts = await withProgress(
|
|
4920
|
+
{ enabled: showProgress, text: "Fetching transcripts..." },
|
|
4921
|
+
async () => client.transcripts.list({
|
|
4922
|
+
limit: Number.parseInt(opts.limit, 10),
|
|
4923
|
+
fromDate,
|
|
4924
|
+
toDate,
|
|
4925
|
+
mine: opts.mine,
|
|
4926
|
+
keyword: opts.keyword,
|
|
4927
|
+
scope: opts.scope,
|
|
4928
|
+
organizers: opts.organizer.length > 0 ? opts.organizer : void 0,
|
|
4929
|
+
participants: participants.length > 0 ? participants : void 0,
|
|
4930
|
+
user_id: opts.userId,
|
|
4931
|
+
channel_id: opts.channel,
|
|
4932
|
+
external: opts.external,
|
|
4933
|
+
includeSentences: opts.includeSentences,
|
|
4934
|
+
includeSummary: opts.includeSummary
|
|
4935
|
+
})
|
|
4936
|
+
);
|
|
3729
4937
|
if (opts.normalize) {
|
|
3730
4938
|
const fullTranscripts = await Promise.all(
|
|
3731
4939
|
transcripts.map((t) => client.transcripts.get(t.id))
|
|
@@ -3734,12 +4942,16 @@ function registerTranscriptsCommand(program2) {
|
|
|
3734
4942
|
output(normalized, format);
|
|
3735
4943
|
return;
|
|
3736
4944
|
}
|
|
4945
|
+
if (opts.includeSentences || opts.includeSummary) {
|
|
4946
|
+
output(transcripts, format);
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
3737
4949
|
const useHumanDuration = format === "table" || format === "plain";
|
|
3738
4950
|
const formatted = transcripts.map((t) => ({
|
|
3739
4951
|
id: t.id,
|
|
3740
4952
|
title: t.title,
|
|
3741
4953
|
date: t.dateString,
|
|
3742
|
-
duration: useHumanDuration ?
|
|
4954
|
+
duration: useHumanDuration ? formatDuration2(t.duration * 60) : Math.round(t.duration),
|
|
3743
4955
|
organizer: t.organizer_email
|
|
3744
4956
|
}));
|
|
3745
4957
|
output(formatted, format);
|
|
@@ -3800,17 +5012,21 @@ function registerTranscriptsCommand(program2) {
|
|
|
3800
5012
|
withErrorHandling(async (opts) => {
|
|
3801
5013
|
const client = getClient(program2);
|
|
3802
5014
|
const format = getOutputFormat(program2);
|
|
5015
|
+
const showProgress = isProgressEnabled(program2);
|
|
3803
5016
|
const { fromDate, toDate } = resolveDateRange(opts);
|
|
3804
5017
|
const filterOptions = buildActionItemFilterOptions(opts);
|
|
3805
|
-
const result = await
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
5018
|
+
const result = await withProgress(
|
|
5019
|
+
{ enabled: showProgress, text: "Exporting action items..." },
|
|
5020
|
+
async () => client.transcripts.exportActionItems({
|
|
5021
|
+
fromDate,
|
|
5022
|
+
toDate,
|
|
5023
|
+
mine: opts.mine,
|
|
5024
|
+
organizers: arrayOrUndefined(opts.organizer),
|
|
5025
|
+
participants: arrayOrUndefined(opts.participant),
|
|
5026
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
5027
|
+
filterOptions
|
|
5028
|
+
})
|
|
5029
|
+
);
|
|
3814
5030
|
if (format === "plain") {
|
|
3815
5031
|
const markdown = formatActionItemsMarkdown(result, buildMarkdownOptions(opts));
|
|
3816
5032
|
return opts.output ? writeToFile(opts.output, markdown, result.totalItems) : writeLine(markdown);
|
|
@@ -3893,10 +5109,11 @@ function getVersion() {
|
|
|
3893
5109
|
}
|
|
3894
5110
|
}
|
|
3895
5111
|
var program = new commander.Command();
|
|
3896
|
-
program.name("fireflies").description("CLI for Fireflies.ai API").version(getVersion()).option("-k, --api-key <key>", "API key (or FIREFLIES_API_KEY env)").option("-o, --output <format>", "Output format: json, jsonl, table, tsv, plain", "json");
|
|
5112
|
+
program.name("fireflies").description("CLI for Fireflies.ai API").version(getVersion()).option("-k, --api-key <key>", "API key (or FIREFLIES_API_KEY env)").option("-o, --output <format>", "Output format: json, jsonl, table, tsv, plain", "json").option("--progress", "Show progress indicators during long operations");
|
|
3897
5113
|
registerTranscriptsCommand(program);
|
|
3898
5114
|
registerSearchCommand(program);
|
|
3899
5115
|
registerInsightsCommand(program);
|
|
5116
|
+
registerDigestCommand(program);
|
|
3900
5117
|
registerMeetingsCommand(program);
|
|
3901
5118
|
registerUsersCommand(program);
|
|
3902
5119
|
registerBitesCommand(program);
|
|
@@ -3904,6 +5121,7 @@ registerAiAppsCommand(program);
|
|
|
3904
5121
|
registerAudioCommand(program);
|
|
3905
5122
|
registerRealtimeCommand(program);
|
|
3906
5123
|
registerExportCommand(program);
|
|
5124
|
+
registerExportBulkCommand(program);
|
|
3907
5125
|
program.parse();
|
|
3908
5126
|
//# sourceMappingURL=index.cjs.map
|
|
3909
5127
|
//# sourceMappingURL=index.cjs.map
|