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.
Files changed (36) hide show
  1. package/dist/cli/index.cjs +1435 -217
  2. package/dist/cli/index.cjs.map +1 -1
  3. package/dist/cli/index.js +1436 -222
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/index.cjs +1372 -392
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +462 -3
  8. package/dist/index.d.ts +462 -3
  9. package/dist/index.js +1357 -393
  10. package/dist/index.js.map +1 -1
  11. package/dist/middleware/express.cjs +459 -16
  12. package/dist/middleware/express.cjs.map +1 -1
  13. package/dist/middleware/express.d.cts +2 -2
  14. package/dist/middleware/express.d.ts +2 -2
  15. package/dist/middleware/express.js +459 -16
  16. package/dist/middleware/express.js.map +1 -1
  17. package/dist/middleware/fastify.cjs +459 -16
  18. package/dist/middleware/fastify.cjs.map +1 -1
  19. package/dist/middleware/fastify.d.cts +2 -2
  20. package/dist/middleware/fastify.d.ts +2 -2
  21. package/dist/middleware/fastify.js +459 -16
  22. package/dist/middleware/fastify.js.map +1 -1
  23. package/dist/middleware/hono.cjs +459 -16
  24. package/dist/middleware/hono.cjs.map +1 -1
  25. package/dist/middleware/hono.d.cts +2 -2
  26. package/dist/middleware/hono.d.ts +2 -2
  27. package/dist/middleware/hono.js +459 -16
  28. package/dist/middleware/hono.js.map +1 -1
  29. package/dist/templates/digest/compact.md +8 -0
  30. package/dist/templates/digest/default.md +44 -0
  31. package/dist/templates/digest/executive.md +22 -0
  32. package/dist/{types-CaHcwnKw.d.ts → types-BMzVSd6w.d.ts} +1 -1
  33. package/dist/{types-BX-3JcRI.d.cts → types-BeXRmVD7.d.cts} +1 -1
  34. package/dist/{types-DIPZmUl3.d.ts → types-D2XsCR5R.d.ts} +120 -1
  35. package/dist/{types-C_XxdRd1.d.cts → types-zVGqyFzP.d.cts} +120 -1
  36. package/package.json +6 -1
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { io } from 'socket.io-client';
2
+ import { readFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
2
5
  import { createHmac, timingSafeEqual } from 'crypto';
3
6
 
4
7
  // src/errors.ts
@@ -596,6 +599,48 @@ function createUsersMutationsAPI(client) {
596
599
  }
597
600
 
598
601
  // src/helpers/pagination.ts
602
+ async function* paginateParallel(fetcher, options = {}) {
603
+ const { concurrency = 3, pageSize = 50, delayMs = 100 } = options;
604
+ const pending = /* @__PURE__ */ new Map();
605
+ let nextPageToFetch = 0;
606
+ let nextPageToYield = 0;
607
+ let foundEnd = false;
608
+ const delay4 = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
609
+ const startFetch = (pageIndex) => {
610
+ const skip = pageIndex * pageSize;
611
+ return fetcher(skip, pageSize);
612
+ };
613
+ const scheduleFetches = async () => {
614
+ while (!foundEnd && pending.size < concurrency) {
615
+ const pageIndex = nextPageToFetch++;
616
+ if (pageIndex > 0 && delayMs > 0) {
617
+ await delay4(delayMs);
618
+ }
619
+ if (foundEnd) break;
620
+ pending.set(pageIndex, startFetch(pageIndex));
621
+ }
622
+ };
623
+ pending.set(0, startFetch(0));
624
+ nextPageToFetch = 1;
625
+ while (pending.has(nextPageToYield)) {
626
+ const pagePromise = pending.get(nextPageToYield);
627
+ if (!pagePromise) break;
628
+ const page = await pagePromise;
629
+ pending.delete(nextPageToYield);
630
+ if (page.length < pageSize) {
631
+ foundEnd = true;
632
+ for (const item of page) {
633
+ yield item;
634
+ }
635
+ break;
636
+ }
637
+ await scheduleFetches();
638
+ for (const item of page) {
639
+ yield item;
640
+ }
641
+ nextPageToYield++;
642
+ }
643
+ }
599
644
  async function* paginate(fetcher, pageSize = 50) {
600
645
  let skip = 0;
601
646
  let hasMore = true;
@@ -1255,6 +1300,62 @@ function getGroupKeyFn(groupBy2) {
1255
1300
  }
1256
1301
  }
1257
1302
 
1303
+ // src/helpers/batch.ts
1304
+ async function* batch(items, processor, options = {}) {
1305
+ const { delayMs = 100, handleRateLimit = true, maxRateLimitRetries = 3 } = options;
1306
+ let isFirst = true;
1307
+ for await (const item of items) {
1308
+ if (!isFirst && delayMs > 0) {
1309
+ await delay(delayMs);
1310
+ }
1311
+ isFirst = false;
1312
+ yield await processWithRetry(item, processor, {
1313
+ handleRateLimit,
1314
+ maxRateLimitRetries
1315
+ });
1316
+ }
1317
+ }
1318
+ async function batchAll(items, processor, options = {}) {
1319
+ const { continueOnError = false, ...batchOptions } = options;
1320
+ const results = [];
1321
+ const errors = [];
1322
+ for await (const batchResult of batch(items, processor, batchOptions)) {
1323
+ if (batchResult.error) {
1324
+ if (!continueOnError) {
1325
+ throw batchResult.error;
1326
+ }
1327
+ errors.push(batchResult.error);
1328
+ } else {
1329
+ results.push(batchResult.result);
1330
+ }
1331
+ }
1332
+ return results;
1333
+ }
1334
+ async function processWithRetry(item, processor, options) {
1335
+ const { handleRateLimit, maxRateLimitRetries } = options;
1336
+ let retries = 0;
1337
+ while (true) {
1338
+ try {
1339
+ const result = await processor(item);
1340
+ return { item, result };
1341
+ } catch (err) {
1342
+ if (handleRateLimit && err instanceof RateLimitError && retries < maxRateLimitRetries) {
1343
+ const waitTime = err.retryAfter ?? 1e3;
1344
+ await delay(waitTime);
1345
+ retries++;
1346
+ continue;
1347
+ }
1348
+ return {
1349
+ item,
1350
+ error: err instanceof Error ? err : new Error(String(err))
1351
+ };
1352
+ }
1353
+ }
1354
+ }
1355
+ function delay(ms) {
1356
+ return new Promise((resolve) => setTimeout(resolve, ms));
1357
+ }
1358
+
1258
1359
  // src/helpers/domain-utils.ts
1259
1360
  function extractDomain(email) {
1260
1361
  const atIndex = email.indexOf("@");
@@ -1270,134 +1371,518 @@ function hasExternalParticipants(participants, internalDomain) {
1270
1371
  });
1271
1372
  }
1272
1373
 
1273
- // src/helpers/meeting-insights.ts
1274
- function analyzeMeetings(transcripts, options = {}) {
1275
- const { speakers, groupBy: groupBy2, topSpeakersCount = 10, topParticipantsCount = 10 } = options;
1276
- if (transcripts.length === 0) {
1277
- return emptyInsights();
1374
+ // src/helpers/markdown.ts
1375
+ var DEFAULT_OPTIONS2 = {
1376
+ includeMetadata: true,
1377
+ includeSummary: true,
1378
+ includeActionItems: true,
1379
+ actionItemFormat: "checkbox",
1380
+ includeTimestamps: false,
1381
+ speakerFormat: "bold",
1382
+ groupBySpeaker: true
1383
+ };
1384
+ var DEFAULT_CHUNKS_OPTIONS = {
1385
+ title: "Live Transcript",
1386
+ includeTimestamps: false,
1387
+ speakerFormat: "bold",
1388
+ groupBySpeaker: true
1389
+ };
1390
+ async function transcriptToMarkdown(transcript, options = {}) {
1391
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
1392
+ const sections = [];
1393
+ if (opts.includeMetadata) {
1394
+ sections.push(formatMetadata(transcript));
1278
1395
  }
1279
- const totalDurationMinutes = sumDurations(transcripts);
1280
- const averageDurationMinutes = totalDurationMinutes / transcripts.length;
1281
- const byDayOfWeek = calculateDayOfWeekStats(transcripts);
1282
- const byTimeGroup = groupBy2 ? calculateTimeGroupStats(transcripts, groupBy2) : void 0;
1283
- const participantData = aggregateParticipants(transcripts);
1284
- const totalUniqueParticipants = participantData.uniqueEmails.size;
1285
- const averageParticipantsPerMeeting = calculateAverageParticipants(transcripts);
1286
- const topParticipants = buildTopParticipants(participantData.stats, topParticipantsCount);
1287
- const speakerData = aggregateSpeakers(transcripts, speakers);
1288
- const totalUniqueSpeakers = speakerData.uniqueNames.size;
1289
- const topSpeakers = buildTopSpeakers(speakerData.stats, topSpeakersCount);
1290
- const { earliestMeeting, latestMeeting } = findDateRange(transcripts);
1291
- return {
1292
- totalMeetings: transcripts.length,
1293
- totalDurationMinutes,
1294
- averageDurationMinutes,
1295
- byDayOfWeek,
1296
- byTimeGroup,
1297
- totalUniqueParticipants,
1298
- averageParticipantsPerMeeting,
1299
- topParticipants,
1300
- totalUniqueSpeakers,
1301
- topSpeakers,
1302
- earliestMeeting,
1303
- latestMeeting
1304
- };
1305
- }
1306
- function emptyInsights() {
1307
- return {
1308
- totalMeetings: 0,
1309
- totalDurationMinutes: 0,
1310
- averageDurationMinutes: 0,
1311
- byDayOfWeek: emptyDayOfWeekStats(),
1312
- byTimeGroup: void 0,
1313
- totalUniqueParticipants: 0,
1314
- averageParticipantsPerMeeting: 0,
1315
- topParticipants: [],
1316
- totalUniqueSpeakers: 0,
1317
- topSpeakers: [],
1318
- earliestMeeting: "",
1319
- latestMeeting: ""
1320
- };
1321
- }
1322
- function emptyDayOfWeekStats() {
1323
- const emptyDay = () => ({ count: 0, totalMinutes: 0 });
1324
- return {
1325
- monday: emptyDay(),
1326
- tuesday: emptyDay(),
1327
- wednesday: emptyDay(),
1328
- thursday: emptyDay(),
1329
- friday: emptyDay(),
1330
- saturday: emptyDay(),
1331
- sunday: emptyDay()
1332
- };
1333
- }
1334
- function sumDurations(transcripts) {
1335
- return transcripts.reduce((sum, t) => sum + (t.duration ?? 0), 0);
1396
+ if (opts.includeSummary && transcript.summary) {
1397
+ sections.push(formatSummary(transcript.summary, opts));
1398
+ }
1399
+ if (transcript.sentences && transcript.sentences.length > 0) {
1400
+ sections.push(formatTranscript(transcript.sentences, opts));
1401
+ }
1402
+ const content = sections.join("\n\n---\n\n");
1403
+ await writeIfOutputPath(content, options.outputPath);
1404
+ return content;
1336
1405
  }
1337
- function calculateDayOfWeekStats(transcripts) {
1338
- const stats = emptyDayOfWeekStats();
1339
- const dayNames = [
1340
- "sunday",
1341
- "monday",
1342
- "tuesday",
1343
- "wednesday",
1344
- "thursday",
1345
- "friday",
1346
- "saturday"
1347
- ];
1348
- for (const t of transcripts) {
1349
- const date = parseDate(t.dateString);
1350
- if (!date) continue;
1351
- const dayIndex = date.getUTCDay();
1352
- const dayName = dayNames[dayIndex];
1353
- if (dayName) {
1354
- stats[dayName].count++;
1355
- stats[dayName].totalMinutes += t.duration ?? 0;
1406
+ async function chunksToMarkdown(chunks, options = {}) {
1407
+ const opts = { ...DEFAULT_CHUNKS_OPTIONS, ...options };
1408
+ const lines = [`# ${opts.title}`];
1409
+ if (chunks.length === 0) {
1410
+ lines.push("", "## Transcript", "", "*No transcription data*");
1411
+ } else {
1412
+ lines.push("", "## Transcript");
1413
+ if (opts.groupBySpeaker) {
1414
+ const groups = groupChunksBySpeaker(chunks);
1415
+ for (const group of groups) {
1416
+ lines.push("", formatChunkGroup(group, opts));
1417
+ }
1418
+ } else {
1419
+ for (const chunk of chunks) {
1420
+ lines.push("", formatChunk(chunk, opts));
1421
+ }
1356
1422
  }
1357
1423
  }
1358
- return stats;
1424
+ const content = lines.join("\n");
1425
+ await writeIfOutputPath(content, options.outputPath);
1426
+ return content;
1359
1427
  }
1360
- function calculateTimeGroupStats(transcripts, groupBy2) {
1361
- const groups = /* @__PURE__ */ new Map();
1362
- for (const t of transcripts) {
1363
- const date = parseDate(t.dateString);
1364
- if (!date) continue;
1365
- const period = formatPeriod(date, groupBy2);
1366
- const existing = groups.get(period) ?? { count: 0, totalMinutes: 0 };
1367
- existing.count++;
1368
- existing.totalMinutes += t.duration ?? 0;
1369
- groups.set(period, existing);
1428
+ function formatMetadata(transcript) {
1429
+ const lines = [`# ${transcript.title || "Untitled Meeting"}`];
1430
+ if (transcript.dateString) {
1431
+ lines.push(`
1432
+ **Date:** ${formatDate(transcript.dateString)}`);
1370
1433
  }
1371
- const result = [];
1372
- for (const [period, data] of groups) {
1373
- result.push({
1374
- period,
1375
- count: data.count,
1376
- totalMinutes: data.totalMinutes,
1377
- averageMinutes: data.totalMinutes / data.count
1378
- });
1434
+ const duration = calculateDuration(transcript);
1435
+ if (duration > 0) {
1436
+ lines.push(`**Duration:** ${formatDuration(duration)}`);
1379
1437
  }
1380
- result.sort((a, b) => a.period.localeCompare(b.period));
1381
- return result;
1438
+ const participants = getParticipantNames(transcript);
1439
+ if (participants.length > 0) {
1440
+ lines.push(`**Participants:** ${participants.join(", ")}`);
1441
+ }
1442
+ return lines.join("\n");
1382
1443
  }
1383
- function formatPeriod(date, groupBy2) {
1384
- const year = date.getUTCFullYear();
1385
- const month = String(date.getUTCMonth() + 1).padStart(2, "0");
1386
- const day = String(date.getUTCDate()).padStart(2, "0");
1387
- switch (groupBy2) {
1388
- case "day":
1389
- return `${year}-${month}-${day}`;
1390
- case "week":
1391
- return getISOWeek(date);
1392
- case "month":
1393
- return `${year}-${month}`;
1444
+ function calculateDuration(transcript) {
1445
+ if (transcript.sentences && transcript.sentences.length > 0) {
1446
+ const lastSentence = transcript.sentences[transcript.sentences.length - 1];
1447
+ if (lastSentence) {
1448
+ return parseFloat(lastSentence.end_time);
1449
+ }
1394
1450
  }
1451
+ return transcript.duration || 0;
1395
1452
  }
1396
- function getISOWeek(date) {
1397
- const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
1398
- d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
1399
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
1400
- const weekNumber = Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
1453
+ function formatSummary(summary, opts) {
1454
+ const sections = ["## Summary"];
1455
+ if (summary.gist) {
1456
+ sections.push("", summary.gist);
1457
+ }
1458
+ if (summary.bullet_gist) {
1459
+ const bullets = parseMultilineField(summary.bullet_gist);
1460
+ if (bullets.length > 0) {
1461
+ sections.push("", "### Key Points");
1462
+ sections.push(bullets.map((p) => `- ${p}`).join("\n"));
1463
+ }
1464
+ }
1465
+ if (opts.includeActionItems && summary.action_items) {
1466
+ const items = parseMultilineField(summary.action_items);
1467
+ if (items.length > 0) {
1468
+ sections.push("", "### Action Items");
1469
+ const prefix = opts.actionItemFormat === "checkbox" ? "- [ ] " : "- ";
1470
+ sections.push(items.map((a) => `${prefix}${a}`).join("\n"));
1471
+ }
1472
+ }
1473
+ return sections.join("\n");
1474
+ }
1475
+ function formatTranscript(sentences, opts) {
1476
+ const lines = ["## Transcript"];
1477
+ if (opts.groupBySpeaker) {
1478
+ const groups = groupSentencesBySpeaker(sentences);
1479
+ for (const group of groups) {
1480
+ lines.push("", formatSpeakerGroup(group, opts));
1481
+ }
1482
+ } else {
1483
+ for (const sentence of sentences) {
1484
+ lines.push("", formatSentence(sentence, opts));
1485
+ }
1486
+ }
1487
+ return lines.join("\n");
1488
+ }
1489
+ function groupSentencesBySpeaker(sentences) {
1490
+ const groups = [];
1491
+ let current = null;
1492
+ for (const sentence of sentences) {
1493
+ if (!current || current.speakerName !== sentence.speaker_name) {
1494
+ current = { speakerName: sentence.speaker_name, sentences: [] };
1495
+ groups.push(current);
1496
+ }
1497
+ current.sentences.push(sentence);
1498
+ }
1499
+ return groups;
1500
+ }
1501
+ function formatSpeakerGroup(group, opts) {
1502
+ const speaker = formatSpeakerName(group.speakerName, opts.speakerFormat);
1503
+ const text = group.sentences.map((s) => s.text).join(" ");
1504
+ const firstSentence = group.sentences[0];
1505
+ if (opts.includeTimestamps && firstSentence) {
1506
+ const timestamp = formatTimestamp(firstSentence.start_time);
1507
+ return `${timestamp} ${speaker} ${text}`;
1508
+ }
1509
+ return `${speaker} ${text}`;
1510
+ }
1511
+ function formatSentence(sentence, opts) {
1512
+ const speaker = formatSpeakerName(sentence.speaker_name, opts.speakerFormat);
1513
+ if (opts.includeTimestamps) {
1514
+ const timestamp = formatTimestamp(sentence.start_time);
1515
+ return `${timestamp} ${speaker} ${sentence.text}`;
1516
+ }
1517
+ return `${speaker} ${sentence.text}`;
1518
+ }
1519
+ function groupChunksBySpeaker(chunks) {
1520
+ const groups = [];
1521
+ let current = null;
1522
+ for (const chunk of chunks) {
1523
+ if (!current || current.speakerName !== chunk.speaker_name) {
1524
+ current = { speakerName: chunk.speaker_name, chunks: [] };
1525
+ groups.push(current);
1526
+ }
1527
+ current.chunks.push(chunk);
1528
+ }
1529
+ return groups;
1530
+ }
1531
+ function formatChunkGroup(group, opts) {
1532
+ const speaker = formatSpeakerName(group.speakerName, opts.speakerFormat);
1533
+ const text = group.chunks.map((c) => c.text).join(" ");
1534
+ const firstChunk = group.chunks[0];
1535
+ if (opts.includeTimestamps && firstChunk) {
1536
+ const timestamp = formatTimestamp(firstChunk.start_time.toString());
1537
+ return `${timestamp} ${speaker} ${text}`;
1538
+ }
1539
+ return `${speaker} ${text}`;
1540
+ }
1541
+ function formatChunk(chunk, opts) {
1542
+ const speaker = formatSpeakerName(chunk.speaker_name, opts.speakerFormat);
1543
+ if (opts.includeTimestamps) {
1544
+ const timestamp = formatTimestamp(chunk.start_time.toString());
1545
+ return `${timestamp} ${speaker} ${chunk.text}`;
1546
+ }
1547
+ return `${speaker} ${chunk.text}`;
1548
+ }
1549
+ function formatSpeakerName(name, format) {
1550
+ switch (format) {
1551
+ case "bold":
1552
+ return `**${name}:**`;
1553
+ case "plain":
1554
+ return `${name}:`;
1555
+ }
1556
+ }
1557
+ function formatTimestamp(startTime) {
1558
+ const seconds = parseFloat(startTime);
1559
+ const mins = Math.floor(seconds / 60);
1560
+ const secs = Math.floor(seconds % 60);
1561
+ return `[${mins}:${secs.toString().padStart(2, "0")}]`;
1562
+ }
1563
+ function formatDuration(seconds) {
1564
+ const hours = Math.floor(seconds / 3600);
1565
+ const mins = Math.floor(seconds % 3600 / 60);
1566
+ if (hours > 0) {
1567
+ return `${hours}h ${mins}m`;
1568
+ }
1569
+ return `${mins} minutes`;
1570
+ }
1571
+ function formatDate(isoString) {
1572
+ return new Date(isoString).toLocaleDateString("en-US", {
1573
+ weekday: "long",
1574
+ year: "numeric",
1575
+ month: "long",
1576
+ day: "numeric"
1577
+ });
1578
+ }
1579
+ function getParticipantNames(transcript) {
1580
+ if (transcript.meeting_attendees?.length) {
1581
+ return transcript.meeting_attendees.map((a) => a.displayName || a.name || a.email).filter(Boolean);
1582
+ }
1583
+ return transcript.speakers?.map((s) => s.name) || [];
1584
+ }
1585
+ function parseMultilineField(value) {
1586
+ return value.split(/\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1587
+ }
1588
+ async function writeIfOutputPath(content, outputPath) {
1589
+ if (outputPath) {
1590
+ const { writeFile } = await import('fs/promises');
1591
+ await writeFile(outputPath, content, "utf-8");
1592
+ }
1593
+ }
1594
+
1595
+ // src/helpers/export-formats.ts
1596
+ var DEFAULT_TEXT_OPTIONS = {
1597
+ includeTimestamps: false,
1598
+ includeMetadata: true
1599
+ };
1600
+ var DEFAULT_CSV_OPTIONS = {
1601
+ includeHeader: true,
1602
+ delimiter: ","
1603
+ };
1604
+ function transcriptToText(transcript, options = {}) {
1605
+ const opts = { ...DEFAULT_TEXT_OPTIONS, ...options };
1606
+ const sections = [];
1607
+ if (opts.includeMetadata) {
1608
+ sections.push(formatTextMetadata(transcript));
1609
+ }
1610
+ if (transcript.sentences && transcript.sentences.length > 0) {
1611
+ sections.push(formatTextTranscript(transcript.sentences, opts));
1612
+ }
1613
+ return sections.join("\n\n");
1614
+ }
1615
+ function transcriptToCsv(transcript, options = {}) {
1616
+ const opts = { ...DEFAULT_CSV_OPTIONS, ...options };
1617
+ const d = opts.delimiter;
1618
+ const lines = [];
1619
+ if (opts.includeHeader) {
1620
+ lines.push(`timestamp${d}speaker${d}text${d}is_question${d}is_task`);
1621
+ }
1622
+ for (const sentence of transcript.sentences) {
1623
+ const isQuestion = Boolean(sentence.ai_filters?.question);
1624
+ const isTask = Boolean(sentence.ai_filters?.task);
1625
+ const row = [
1626
+ sentence.start_time,
1627
+ escapeCsvField(sentence.speaker_name, d),
1628
+ escapeCsvField(sentence.text, d),
1629
+ String(isQuestion),
1630
+ String(isTask)
1631
+ ];
1632
+ lines.push(row.join(d));
1633
+ }
1634
+ return lines.join("\n");
1635
+ }
1636
+ function sanitizeFilename(title) {
1637
+ if (!title.trim()) {
1638
+ return "untitled";
1639
+ }
1640
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 100);
1641
+ }
1642
+ function generateExportFilename(transcript, extension) {
1643
+ const sanitizedTitle = sanitizeFilename(transcript.title);
1644
+ let datePrefix = "";
1645
+ if (transcript.dateString) {
1646
+ try {
1647
+ const date = new Date(transcript.dateString);
1648
+ if (!Number.isNaN(date.getTime())) {
1649
+ datePrefix = `${date.toISOString().slice(0, 10)}-`;
1650
+ }
1651
+ } catch {
1652
+ }
1653
+ }
1654
+ return `${datePrefix}${sanitizedTitle}.${extension}`;
1655
+ }
1656
+ async function exportTranscript(transcript, format) {
1657
+ switch (format) {
1658
+ case "markdown":
1659
+ return transcriptToMarkdown(transcript);
1660
+ case "json":
1661
+ return JSON.stringify(transcript, null, 2);
1662
+ case "txt":
1663
+ return transcriptToText(transcript);
1664
+ case "csv":
1665
+ return transcriptToCsv(transcript);
1666
+ }
1667
+ }
1668
+ async function createZipArchive(files) {
1669
+ const archiver = await import('archiver');
1670
+ const { Writable } = await import('stream');
1671
+ return new Promise((resolve, reject) => {
1672
+ const chunks = [];
1673
+ const archive = archiver.default("zip", { zlib: { level: 9 } });
1674
+ const writable = new Writable({
1675
+ write(chunk, _encoding, callback) {
1676
+ chunks.push(chunk);
1677
+ callback();
1678
+ }
1679
+ });
1680
+ writable.on("finish", () => {
1681
+ resolve(Buffer.concat(chunks));
1682
+ });
1683
+ archive.on("error", reject);
1684
+ archive.pipe(writable);
1685
+ for (const file of files) {
1686
+ archive.append(file.content, { name: file.filename });
1687
+ }
1688
+ archive.finalize();
1689
+ });
1690
+ }
1691
+ function formatTextMetadata(transcript) {
1692
+ const lines = [];
1693
+ lines.push(transcript.title || "Untitled Meeting");
1694
+ if (transcript.dateString) {
1695
+ lines.push(`Date: ${formatDate2(transcript.dateString)}`);
1696
+ }
1697
+ const participants = getParticipantNames2(transcript);
1698
+ if (participants.length > 0) {
1699
+ lines.push(`Participants: ${participants.join(", ")}`);
1700
+ }
1701
+ return lines.join("\n");
1702
+ }
1703
+ function formatTextTranscript(sentences, opts) {
1704
+ const groups = groupSentencesBySpeaker2(sentences);
1705
+ const lines = [];
1706
+ for (const group of groups) {
1707
+ const text = group.sentences.map((s) => s.text).join(" ");
1708
+ const speaker = group.speakerName || "Unknown";
1709
+ const firstSentence = group.sentences[0];
1710
+ if (opts.includeTimestamps && firstSentence) {
1711
+ const timestamp = formatTimestamp2(firstSentence.start_time);
1712
+ lines.push(`${timestamp} ${speaker}: ${text}`);
1713
+ } else {
1714
+ lines.push(`${speaker}: ${text}`);
1715
+ }
1716
+ }
1717
+ return lines.join("\n");
1718
+ }
1719
+ function groupSentencesBySpeaker2(sentences) {
1720
+ const groups = [];
1721
+ let current = null;
1722
+ for (const sentence of sentences) {
1723
+ if (!current || current.speakerName !== sentence.speaker_name) {
1724
+ current = { speakerName: sentence.speaker_name, sentences: [] };
1725
+ groups.push(current);
1726
+ }
1727
+ current.sentences.push(sentence);
1728
+ }
1729
+ return groups;
1730
+ }
1731
+ function formatTimestamp2(startTime) {
1732
+ const seconds = parseFloat(startTime);
1733
+ const mins = Math.floor(seconds / 60);
1734
+ const secs = Math.floor(seconds % 60);
1735
+ return `[${mins}:${secs.toString().padStart(2, "0")}]`;
1736
+ }
1737
+ function formatDate2(isoString) {
1738
+ return new Date(isoString).toLocaleDateString("en-US", {
1739
+ weekday: "long",
1740
+ year: "numeric",
1741
+ month: "long",
1742
+ day: "numeric"
1743
+ });
1744
+ }
1745
+ function getParticipantNames2(transcript) {
1746
+ if (transcript.meeting_attendees?.length) {
1747
+ return transcript.meeting_attendees.map((a) => a.displayName || a.name || a.email).filter(Boolean);
1748
+ }
1749
+ return transcript.speakers?.map((s) => s.name) || [];
1750
+ }
1751
+ function escapeCsvField(field, delimiter) {
1752
+ if (field.includes('"') || field.includes(delimiter) || field.includes("\n")) {
1753
+ return `"${field.replace(/"/g, '""')}"`;
1754
+ }
1755
+ return field;
1756
+ }
1757
+
1758
+ // src/helpers/meeting-insights.ts
1759
+ function analyzeMeetings(transcripts, options = {}) {
1760
+ const { speakers, groupBy: groupBy2, topSpeakersCount = 10, topParticipantsCount = 10 } = options;
1761
+ if (transcripts.length === 0) {
1762
+ return emptyInsights();
1763
+ }
1764
+ const totalDurationMinutes = sumDurations(transcripts);
1765
+ const averageDurationMinutes = totalDurationMinutes / transcripts.length;
1766
+ const byDayOfWeek = calculateDayOfWeekStats(transcripts);
1767
+ const byTimeGroup = groupBy2 ? calculateTimeGroupStats(transcripts, groupBy2) : void 0;
1768
+ const participantData = aggregateParticipants(transcripts);
1769
+ const totalUniqueParticipants = participantData.uniqueEmails.size;
1770
+ const averageParticipantsPerMeeting = calculateAverageParticipants(transcripts);
1771
+ const topParticipants = buildTopParticipants(participantData.stats, topParticipantsCount);
1772
+ const speakerData = aggregateSpeakers(transcripts, speakers);
1773
+ const totalUniqueSpeakers = speakerData.uniqueNames.size;
1774
+ const topSpeakers = buildTopSpeakers(speakerData.stats, topSpeakersCount);
1775
+ const { earliestMeeting, latestMeeting } = findDateRange(transcripts);
1776
+ return {
1777
+ totalMeetings: transcripts.length,
1778
+ totalDurationMinutes,
1779
+ averageDurationMinutes,
1780
+ byDayOfWeek,
1781
+ byTimeGroup,
1782
+ totalUniqueParticipants,
1783
+ averageParticipantsPerMeeting,
1784
+ topParticipants,
1785
+ totalUniqueSpeakers,
1786
+ topSpeakers,
1787
+ earliestMeeting,
1788
+ latestMeeting
1789
+ };
1790
+ }
1791
+ function emptyInsights() {
1792
+ return {
1793
+ totalMeetings: 0,
1794
+ totalDurationMinutes: 0,
1795
+ averageDurationMinutes: 0,
1796
+ byDayOfWeek: emptyDayOfWeekStats(),
1797
+ byTimeGroup: void 0,
1798
+ totalUniqueParticipants: 0,
1799
+ averageParticipantsPerMeeting: 0,
1800
+ topParticipants: [],
1801
+ totalUniqueSpeakers: 0,
1802
+ topSpeakers: [],
1803
+ earliestMeeting: "",
1804
+ latestMeeting: ""
1805
+ };
1806
+ }
1807
+ function emptyDayOfWeekStats() {
1808
+ const emptyDay = () => ({ count: 0, totalMinutes: 0 });
1809
+ return {
1810
+ monday: emptyDay(),
1811
+ tuesday: emptyDay(),
1812
+ wednesday: emptyDay(),
1813
+ thursday: emptyDay(),
1814
+ friday: emptyDay(),
1815
+ saturday: emptyDay(),
1816
+ sunday: emptyDay()
1817
+ };
1818
+ }
1819
+ function sumDurations(transcripts) {
1820
+ return transcripts.reduce((sum, t) => sum + (t.duration ?? 0), 0);
1821
+ }
1822
+ function calculateDayOfWeekStats(transcripts) {
1823
+ const stats = emptyDayOfWeekStats();
1824
+ const dayNames = [
1825
+ "sunday",
1826
+ "monday",
1827
+ "tuesday",
1828
+ "wednesday",
1829
+ "thursday",
1830
+ "friday",
1831
+ "saturday"
1832
+ ];
1833
+ for (const t of transcripts) {
1834
+ const date = parseDate(t.dateString);
1835
+ if (!date) continue;
1836
+ const dayIndex = date.getUTCDay();
1837
+ const dayName = dayNames[dayIndex];
1838
+ if (dayName) {
1839
+ stats[dayName].count++;
1840
+ stats[dayName].totalMinutes += t.duration ?? 0;
1841
+ }
1842
+ }
1843
+ return stats;
1844
+ }
1845
+ function calculateTimeGroupStats(transcripts, groupBy2) {
1846
+ const groups = /* @__PURE__ */ new Map();
1847
+ for (const t of transcripts) {
1848
+ const date = parseDate(t.dateString);
1849
+ if (!date) continue;
1850
+ const period = formatPeriod(date, groupBy2);
1851
+ const existing = groups.get(period) ?? { count: 0, totalMinutes: 0 };
1852
+ existing.count++;
1853
+ existing.totalMinutes += t.duration ?? 0;
1854
+ groups.set(period, existing);
1855
+ }
1856
+ const result = [];
1857
+ for (const [period, data] of groups) {
1858
+ result.push({
1859
+ period,
1860
+ count: data.count,
1861
+ totalMinutes: data.totalMinutes,
1862
+ averageMinutes: data.totalMinutes / data.count
1863
+ });
1864
+ }
1865
+ result.sort((a, b) => a.period.localeCompare(b.period));
1866
+ return result;
1867
+ }
1868
+ function formatPeriod(date, groupBy2) {
1869
+ const year = date.getUTCFullYear();
1870
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
1871
+ const day = String(date.getUTCDate()).padStart(2, "0");
1872
+ switch (groupBy2) {
1873
+ case "day":
1874
+ return `${year}-${month}-${day}`;
1875
+ case "week":
1876
+ return getISOWeek(date);
1877
+ case "month":
1878
+ return `${year}-${month}`;
1879
+ }
1880
+ }
1881
+ function getISOWeek(date) {
1882
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
1883
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
1884
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
1885
+ const weekNumber = Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
1401
1886
  return `${d.getUTCFullYear()}-W${String(weekNumber).padStart(2, "0")}`;
1402
1887
  }
1403
1888
  function aggregateParticipants(transcripts) {
@@ -1743,6 +2228,21 @@ var TRANSCRIPT_LIST_FIELDS = `
1743
2228
  summary_status
1744
2229
  }
1745
2230
  `;
2231
+ function buildListFields(params) {
2232
+ const includeSentences = params?.includeSentences === true;
2233
+ const includeSummary = params?.includeSummary === true;
2234
+ if (!includeSentences && !includeSummary) {
2235
+ return TRANSCRIPT_LIST_FIELDS;
2236
+ }
2237
+ let fields = TRANSCRIPT_BASE_FIELDS;
2238
+ if (includeSentences) {
2239
+ fields += SENTENCES_FIELDS;
2240
+ }
2241
+ if (includeSummary) {
2242
+ fields += SUMMARY_FIELDS;
2243
+ }
2244
+ return fields;
2245
+ }
1746
2246
  function createTranscriptsAPI(client) {
1747
2247
  return {
1748
2248
  async get(id, params) {
@@ -1758,6 +2258,7 @@ function createTranscriptsAPI(client) {
1758
2258
  return normalizeTranscript(data.transcript);
1759
2259
  },
1760
2260
  async list(params) {
2261
+ const fields = buildListFields(params);
1761
2262
  const query = `
1762
2263
  query ListTranscripts(
1763
2264
  $keyword: String
@@ -1795,13 +2296,23 @@ function createTranscriptsAPI(client) {
1795
2296
  participant_email: $participant_email
1796
2297
  date: $date
1797
2298
  ) {
1798
- ${TRANSCRIPT_LIST_FIELDS}
2299
+ ${fields}
1799
2300
  }
1800
2301
  }
1801
2302
  `;
2303
+ let internalDomain;
2304
+ if (params?.external) {
2305
+ const userQuery = "query { user { email } }";
2306
+ const userData = await client.execute(userQuery);
2307
+ internalDomain = extractDomain(userData.user.email);
2308
+ }
1802
2309
  const variables = buildListVariables(params);
1803
2310
  const data = await client.execute(query, variables);
1804
- return data.transcripts.map(normalizeTranscript);
2311
+ let results = data.transcripts.map(normalizeTranscript);
2312
+ if (internalDomain) {
2313
+ results = results.filter((t) => hasExternalParticipants(t.participants, internalDomain));
2314
+ }
2315
+ return results;
1805
2316
  },
1806
2317
  async getSummary(id) {
1807
2318
  const query = `
@@ -1848,9 +2359,10 @@ function createTranscriptsAPI(client) {
1848
2359
  } = params;
1849
2360
  const transcripts = [];
1850
2361
  for await (const t of this.listAll({
2362
+ ...listParams,
1851
2363
  keyword: query,
1852
2364
  scope,
1853
- ...listParams
2365
+ includeSentences: true
1854
2366
  })) {
1855
2367
  transcripts.push(t);
1856
2368
  if (limit && transcripts.length >= limit) break;
@@ -1858,8 +2370,7 @@ function createTranscriptsAPI(client) {
1858
2370
  const allMatches = [];
1859
2371
  let transcriptsWithMatches = 0;
1860
2372
  for (const t of transcripts) {
1861
- const full = await this.get(t.id, { includeSentences: true });
1862
- const matches = searchTranscript(full, {
2373
+ const matches = searchTranscript(t, {
1863
2374
  query,
1864
2375
  caseSensitive,
1865
2376
  speakers,
@@ -1911,13 +2422,13 @@ function createTranscriptsAPI(client) {
1911
2422
  organizers,
1912
2423
  participants,
1913
2424
  user_id,
1914
- channel_id
2425
+ channel_id,
2426
+ includeSentences: true
1915
2427
  })) {
1916
2428
  if (internalDomain && !hasExternalParticipants(t.participants, internalDomain)) {
1917
2429
  continue;
1918
2430
  }
1919
- const full = await this.get(t.id, { includeSentences: true, includeSummary: false });
1920
- transcripts.push(full);
2431
+ transcripts.push(t);
1921
2432
  if (limit && transcripts.length >= limit) break;
1922
2433
  }
1923
2434
  return analyzeMeetings(transcripts, {
@@ -1935,13 +2446,20 @@ function createTranscriptsAPI(client) {
1935
2446
  toDate,
1936
2447
  mine,
1937
2448
  organizers,
1938
- participants
2449
+ participants,
2450
+ includeSummary: true
1939
2451
  })) {
1940
- const full = await this.get(t.id, { includeSentences: false, includeSummary: true });
1941
- transcripts.push(full);
2452
+ transcripts.push(t);
1942
2453
  if (limit && transcripts.length >= limit) break;
1943
2454
  }
1944
2455
  return aggregateActionItems(transcripts, {}, filterOptions);
2456
+ },
2457
+ async bulkExport(params = {}) {
2458
+ const { format = "markdown", asZip = false, onProgress } = params;
2459
+ const transcriptIds = await collectTranscriptIds(client, this, params);
2460
+ const files = await convertTranscripts(this, transcriptIds, format, onProgress);
2461
+ const zip = asZip ? await createZipArchive(files) : void 0;
2462
+ return { files, zip, format, totalExported: files.length };
1945
2463
  }
1946
2464
  };
1947
2465
  }
@@ -2020,6 +2538,50 @@ function buildListVariables(params) {
2020
2538
  date: params.date
2021
2539
  };
2022
2540
  }
2541
+ async function collectTranscriptIds(client, api, params) {
2542
+ const { ids, fromDate, toDate, mine, organizers, participants, external, limit } = params;
2543
+ if (ids?.length) {
2544
+ return ids;
2545
+ }
2546
+ let internalDomain;
2547
+ if (external) {
2548
+ const userQuery = "query { user { email } }";
2549
+ const userData = await client.execute(userQuery);
2550
+ internalDomain = extractDomain(userData.user.email);
2551
+ }
2552
+ const result = [];
2553
+ for await (const t of api.listAll({ fromDate, toDate, mine, organizers, participants })) {
2554
+ if (internalDomain && !hasExternalParticipants(t.participants, internalDomain)) {
2555
+ continue;
2556
+ }
2557
+ result.push(t.id);
2558
+ if (limit && result.length >= limit) break;
2559
+ }
2560
+ return result;
2561
+ }
2562
+ async function convertTranscripts(api, transcriptIds, format, onProgress) {
2563
+ const files = [];
2564
+ const extension = format === "markdown" ? "md" : format;
2565
+ let completed = 0;
2566
+ const total = transcriptIds.length;
2567
+ for await (const result of batch(
2568
+ transcriptIds,
2569
+ async (id) => {
2570
+ const transcript = await api.get(id);
2571
+ const content = await exportTranscript(transcript, format);
2572
+ const filename = generateExportFilename(transcript, extension);
2573
+ return { id, title: transcript.title, filename, content };
2574
+ },
2575
+ { delayMs: 100 }
2576
+ )) {
2577
+ if (!result.error) {
2578
+ files.push(result.result);
2579
+ }
2580
+ completed++;
2581
+ onProgress?.(completed, total);
2582
+ }
2583
+ return files;
2584
+ }
2023
2585
 
2024
2586
  // src/graphql/queries/users.ts
2025
2587
  var USER_FIELDS = `
@@ -2558,61 +3120,684 @@ var TranscriptAccumulator = class {
2558
3120
  }
2559
3121
  };
2560
3122
 
2561
- // src/helpers/batch.ts
2562
- async function* batch(items, processor, options = {}) {
2563
- const { delayMs = 100, handleRateLimit = true, maxRateLimitRetries = 3 } = options;
2564
- let isFirst = true;
2565
- for await (const item of items) {
2566
- if (!isFirst && delayMs > 0) {
2567
- await delay(delayMs);
3123
+ // src/helpers/digest.ts
3124
+ var DAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
3125
+ function calculateStats(transcripts) {
3126
+ if (transcripts.length === 0) {
3127
+ return emptyStats();
3128
+ }
3129
+ const totalMinutes = sumDurations2(transcripts);
3130
+ const meetingsByDay = calculateMeetingsByDay(transcripts);
3131
+ const busiestDay = findBusiestDay(meetingsByDay);
3132
+ return {
3133
+ totalMeetings: transcripts.length,
3134
+ totalMinutes,
3135
+ averageDuration: totalMinutes / transcripts.length,
3136
+ busiestDay,
3137
+ meetingsByDay
3138
+ };
3139
+ }
3140
+ function emptyStats() {
3141
+ return {
3142
+ totalMeetings: 0,
3143
+ totalMinutes: 0,
3144
+ averageDuration: 0,
3145
+ busiestDay: "",
3146
+ meetingsByDay: {}
3147
+ };
3148
+ }
3149
+ function sumDurations2(transcripts) {
3150
+ return transcripts.reduce((sum, t) => sum + (t.duration ?? 0), 0);
3151
+ }
3152
+ function calculateMeetingsByDay(transcripts) {
3153
+ const counts = {};
3154
+ for (const t of transcripts) {
3155
+ const date = parseDate2(t.dateString);
3156
+ if (!date) continue;
3157
+ const dayName = DAY_NAMES[date.getUTCDay()];
3158
+ if (dayName) {
3159
+ counts[dayName] = (counts[dayName] ?? 0) + 1;
2568
3160
  }
2569
- isFirst = false;
2570
- yield await processWithRetry(item, processor, {
2571
- handleRateLimit,
2572
- maxRateLimitRetries
3161
+ }
3162
+ return counts;
3163
+ }
3164
+ function findBusiestDay(meetingsByDay) {
3165
+ let busiestDay = "";
3166
+ let maxCount = 0;
3167
+ for (const [day, count] of Object.entries(meetingsByDay)) {
3168
+ if (count > maxCount) {
3169
+ maxCount = count;
3170
+ busiestDay = day;
3171
+ }
3172
+ }
3173
+ return busiestDay;
3174
+ }
3175
+ function parseDate2(dateString) {
3176
+ if (!dateString) return null;
3177
+ const date = new Date(dateString);
3178
+ return Number.isNaN(date.getTime()) ? null : date;
3179
+ }
3180
+ var MAX_KEY_POINTS_PER_MEETING = 5;
3181
+ function extractHighlights(transcripts) {
3182
+ const highlights = [];
3183
+ for (const t of transcripts) {
3184
+ const overview = t.summary?.overview;
3185
+ if (!overview || overview.trim().length === 0) continue;
3186
+ const keyPoints = extractKeyPoints(overview);
3187
+ if (keyPoints.length === 0) continue;
3188
+ highlights.push({
3189
+ meetingId: t.id,
3190
+ meetingTitle: t.title,
3191
+ meetingDate: t.dateString,
3192
+ keyPoints,
3193
+ decisions: extractDecisions(t)
2573
3194
  });
2574
3195
  }
3196
+ return highlights;
3197
+ }
3198
+ function extractKeyPoints(overview) {
3199
+ const sentences = overview.split(/(?<=[.!?])\s+/).map((s) => s.trim().replace(/[.!?]$/, "")).map((s) => s.replace(/^-\s*/, "")).filter((s) => s.length > 0);
3200
+ return sentences.slice(0, MAX_KEY_POINTS_PER_MEETING);
3201
+ }
3202
+ function extractDecisions(transcript) {
3203
+ const decisions = [];
3204
+ const overview = transcript.summary?.overview ?? "";
3205
+ const decisionPattern = /(?:^|[.!?]\s*)([^.!?]*(?:decided?|decision|agreed|approved)[^.!?]*)/gi;
3206
+ for (; ; ) {
3207
+ const match = decisionPattern.exec(overview);
3208
+ if (!match) break;
3209
+ if (match[1]) {
3210
+ decisions.push(match[1].trim());
3211
+ }
3212
+ }
3213
+ return decisions;
2575
3214
  }
2576
- async function batchAll(items, processor, options = {}) {
2577
- const { continueOnError = false, ...batchOptions } = options;
2578
- const results = [];
2579
- const errors = [];
2580
- for await (const batchResult of batch(items, processor, batchOptions)) {
2581
- if (batchResult.error) {
2582
- if (!continueOnError) {
2583
- throw batchResult.error;
3215
+ function buildNameLookup(transcripts) {
3216
+ const namesByEmail = /* @__PURE__ */ new Map();
3217
+ for (const t of transcripts) {
3218
+ for (const attendee of t.meeting_attendees ?? []) {
3219
+ if (!attendee.email) continue;
3220
+ const normalizedEmail = attendee.email.toLowerCase();
3221
+ const name = attendee.displayName || attendee.name;
3222
+ if (name && !namesByEmail.has(normalizedEmail)) {
3223
+ namesByEmail.set(normalizedEmail, name);
2584
3224
  }
2585
- errors.push(batchResult.error);
2586
- } else {
2587
- results.push(batchResult.result);
2588
3225
  }
2589
3226
  }
2590
- return results;
3227
+ return namesByEmail;
2591
3228
  }
2592
- async function processWithRetry(item, processor, options) {
2593
- const { handleRateLimit, maxRateLimitRetries } = options;
2594
- let retries = 0;
2595
- while (true) {
2596
- try {
2597
- const result = await processor(item);
2598
- return { item, result };
2599
- } catch (err) {
2600
- if (handleRateLimit && err instanceof RateLimitError && retries < maxRateLimitRetries) {
2601
- const waitTime = err.retryAfter ?? 1e3;
2602
- await delay(waitTime);
2603
- retries++;
2604
- continue;
2605
- }
2606
- return {
2607
- item,
2608
- error: err instanceof Error ? err : new Error(String(err))
3229
+ function countParticipation(transcripts, namesByEmail) {
3230
+ const stats = /* @__PURE__ */ new Map();
3231
+ for (const t of transcripts) {
3232
+ const seenInMeeting = /* @__PURE__ */ new Set();
3233
+ for (const email of t.participants ?? []) {
3234
+ const normalizedEmail = email.toLowerCase();
3235
+ if (seenInMeeting.has(normalizedEmail)) continue;
3236
+ seenInMeeting.add(normalizedEmail);
3237
+ const existing = stats.get(normalizedEmail) ?? {
3238
+ name: namesByEmail.get(normalizedEmail) || extractNameFromEmail(email),
3239
+ meetingCount: 0,
3240
+ totalMinutes: 0
3241
+ };
3242
+ existing.meetingCount++;
3243
+ existing.totalMinutes += t.duration ?? 0;
3244
+ stats.set(normalizedEmail, existing);
3245
+ }
3246
+ }
3247
+ return stats;
3248
+ }
3249
+ function aggregateParticipants2(transcripts) {
3250
+ const namesByEmail = buildNameLookup(transcripts);
3251
+ const stats = countParticipation(transcripts, namesByEmail);
3252
+ return Array.from(stats, ([email, data]) => ({
3253
+ email,
3254
+ name: data.name,
3255
+ meetingCount: data.meetingCount,
3256
+ totalMinutes: data.totalMinutes
3257
+ })).sort((a, b) => b.meetingCount - a.meetingCount);
3258
+ }
3259
+ function extractNameFromEmail(email) {
3260
+ const localPart = email.split("@")[0] ?? email;
3261
+ return localPart;
3262
+ }
3263
+ function buildParticipantInfoList(emails, attendees) {
3264
+ const attendeeMap = /* @__PURE__ */ new Map();
3265
+ for (const attendee of attendees) {
3266
+ if (attendee.email) {
3267
+ attendeeMap.set(attendee.email.toLowerCase(), attendee);
3268
+ }
3269
+ }
3270
+ return emails.map((email) => {
3271
+ const normalizedEmail = email.toLowerCase();
3272
+ const attendee = attendeeMap.get(normalizedEmail);
3273
+ return {
3274
+ email: normalizedEmail,
3275
+ name: attendee?.displayName || attendee?.name || extractNameFromEmail(normalizedEmail)
3276
+ };
3277
+ });
3278
+ }
3279
+ function aggregateActionItemsForDigest(transcripts) {
3280
+ if (transcripts.length === 0) {
3281
+ return emptyActionItems();
3282
+ }
3283
+ const byAssignee = {};
3284
+ const byMeeting = [];
3285
+ const unassigned = [];
3286
+ const withDueDates = [];
3287
+ let total = 0;
3288
+ for (const t of transcripts) {
3289
+ const result = extractActionItems(t);
3290
+ const meetingItems = [];
3291
+ for (const item of result.items) {
3292
+ const digestItem = {
3293
+ ...item,
3294
+ transcriptId: t.id,
3295
+ transcriptTitle: t.title,
3296
+ transcriptDate: t.dateString
2609
3297
  };
3298
+ total++;
3299
+ meetingItems.push(digestItem);
3300
+ if (item.assignee) {
3301
+ const existing = byAssignee[item.assignee] ?? [];
3302
+ existing.push(digestItem);
3303
+ byAssignee[item.assignee] = existing;
3304
+ } else {
3305
+ unassigned.push(digestItem);
3306
+ }
3307
+ if (item.dueDate) {
3308
+ withDueDates.push(digestItem);
3309
+ }
3310
+ }
3311
+ if (meetingItems.length > 0) {
3312
+ byMeeting.push({
3313
+ id: t.id,
3314
+ title: t.title,
3315
+ date: t.dateString,
3316
+ duration: t.duration ?? 0,
3317
+ participants: buildParticipantInfoList(t.participants ?? [], t.meeting_attendees ?? []),
3318
+ items: meetingItems
3319
+ });
3320
+ }
3321
+ }
3322
+ return { total, byAssignee, byMeeting, unassigned, withDueDates };
3323
+ }
3324
+ function emptyActionItems() {
3325
+ return {
3326
+ total: 0,
3327
+ byAssignee: {},
3328
+ byMeeting: [],
3329
+ unassigned: [],
3330
+ withDueDates: []
3331
+ };
3332
+ }
3333
+ function buildDigest(transcripts, options = {}) {
3334
+ const { includeActionItems = true, includeHighlights = true, includeStats = true } = options;
3335
+ if (transcripts.length === 0) {
3336
+ return emptyDigest();
3337
+ }
3338
+ const stats = includeStats ? calculateStats(transcripts) : emptyStats();
3339
+ const actionItems = includeActionItems ? aggregateActionItemsForDigest(transcripts) : emptyActionItems();
3340
+ const highlights = includeHighlights ? extractHighlights(transcripts) : [];
3341
+ const participants = aggregateParticipants2(transcripts);
3342
+ const meetings = transcripts.map(toMeetingSummary);
3343
+ const period = calculatePeriod(transcripts);
3344
+ const totalDuration = sumDurations2(transcripts);
3345
+ return {
3346
+ period,
3347
+ totalMeetings: transcripts.length,
3348
+ totalDuration,
3349
+ stats,
3350
+ actionItems,
3351
+ highlights,
3352
+ participants,
3353
+ meetings
3354
+ };
3355
+ }
3356
+ function emptyDigest() {
3357
+ return {
3358
+ period: { from: "", to: "" },
3359
+ totalMeetings: 0,
3360
+ totalDuration: 0,
3361
+ stats: emptyStats(),
3362
+ actionItems: emptyActionItems(),
3363
+ highlights: [],
3364
+ participants: [],
3365
+ meetings: []
3366
+ };
3367
+ }
3368
+ function toMeetingSummary(transcript) {
3369
+ return {
3370
+ id: transcript.id,
3371
+ title: transcript.title,
3372
+ date: transcript.dateString,
3373
+ duration: transcript.duration ?? 0,
3374
+ participants: (transcript.participants ?? []).length
3375
+ };
3376
+ }
3377
+ function calculatePeriod(transcripts) {
3378
+ let earliest = null;
3379
+ let latest = null;
3380
+ for (const t of transcripts) {
3381
+ const date = parseDate2(t.dateString);
3382
+ if (!date) continue;
3383
+ if (!earliest || date < earliest) {
3384
+ earliest = date;
3385
+ }
3386
+ if (!latest || date > latest) {
3387
+ latest = date;
3388
+ }
3389
+ }
3390
+ return {
3391
+ from: earliest ? formatDateOnly2(earliest) : "",
3392
+ to: latest ? formatDateOnly2(latest) : ""
3393
+ };
3394
+ }
3395
+ function formatDateOnly2(date) {
3396
+ const year = date.getUTCFullYear();
3397
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
3398
+ const day = String(date.getUTCDate()).padStart(2, "0");
3399
+ return `${year}-${month}-${day}`;
3400
+ }
3401
+ var BUILT_IN_TEMPLATES = ["default", "compact", "executive"];
3402
+ function getTemplatesDir() {
3403
+ const currentDir = dirname(fileURLToPath(import.meta.url));
3404
+ return join(currentDir, "..", "templates", "digest");
3405
+ }
3406
+ function isBuiltInTemplate(name) {
3407
+ return BUILT_IN_TEMPLATES.includes(name);
3408
+ }
3409
+ function loadTemplate(templateOption) {
3410
+ const templateName = templateOption ?? "default";
3411
+ if (isBuiltInTemplate(templateName)) {
3412
+ const templatePath = join(getTemplatesDir(), `${templateName}.md`);
3413
+ try {
3414
+ return readFileSync(templatePath, "utf-8");
3415
+ } catch {
3416
+ return getInlineTemplate(templateName);
3417
+ }
3418
+ }
3419
+ if (templateOption && (templateOption.startsWith("#") || templateOption.includes("{{"))) {
3420
+ return templateOption;
3421
+ }
3422
+ try {
3423
+ return readFileSync(templateName, "utf-8");
3424
+ } catch {
3425
+ if (templateName.includes("/") || templateName.includes("\\") || templateName.endsWith(".md")) {
3426
+ throw new Error(`Template file not found: ${templateName}`);
3427
+ }
3428
+ return templateName;
3429
+ }
3430
+ }
3431
+ function getInlineTemplate(name) {
3432
+ switch (name) {
3433
+ case "compact":
3434
+ return COMPACT_TEMPLATE;
3435
+ case "executive":
3436
+ return EXECUTIVE_TEMPLATE;
3437
+ default:
3438
+ return DEFAULT_TEMPLATE;
3439
+ }
3440
+ }
3441
+ function renderDigest(digest, options) {
3442
+ const template = loadTemplate(options?.template);
3443
+ return renderTemplate(template, digest);
3444
+ }
3445
+ function formatDurationHtml(minutes) {
3446
+ const hours = Math.floor(minutes / 60);
3447
+ const mins = minutes % 60;
3448
+ return `${hours}h ${mins}m`;
3449
+ }
3450
+ function escapeHtml(str) {
3451
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3452
+ }
3453
+ function renderActionItemsHtml(digest) {
3454
+ const allActionItems = digest.actionItems.byMeeting.flatMap((m) => m.items);
3455
+ return allActionItems.map(
3456
+ (item) => `
3457
+ <li class="action-item">
3458
+ <input type="checkbox" disabled />
3459
+ <span class="action-text">${escapeHtml(item.text)}</span>
3460
+ ${item.assignee ? `<span class="assignee">(${escapeHtml(item.assignee)})</span>` : ""}
3461
+ ${item.dueDate ? `<span class="due-date">due ${escapeHtml(item.dueDate)}</span>` : ""}
3462
+ </li>`
3463
+ ).join("\n");
3464
+ }
3465
+ function renderMeetingsHtml(digest) {
3466
+ return digest.meetings.map(
3467
+ (m) => `
3468
+ <tr>
3469
+ <td>${escapeHtml(m.title)}</td>
3470
+ <td>${escapeHtml(m.date)}</td>
3471
+ <td>${formatDurationHtml(m.duration)}</td>
3472
+ <td>${m.participants}</td>
3473
+ </tr>`
3474
+ ).join("\n");
3475
+ }
3476
+ function renderHighlightsHtml(digest) {
3477
+ return digest.highlights.map(
3478
+ (h) => `
3479
+ <div class="highlight">
3480
+ <h4>${escapeHtml(h.meetingTitle)} (${escapeHtml(h.meetingDate)})</h4>
3481
+ <ul>
3482
+ ${h.keyPoints.map((p) => `<li>${escapeHtml(p)}</li>`).join("\n")}
3483
+ </ul>
3484
+ </div>`
3485
+ ).join("\n");
3486
+ }
3487
+ function renderParticipantsHtml(digest) {
3488
+ return digest.participants.map(
3489
+ (p) => `
3490
+ <tr>
3491
+ <td>${escapeHtml(p.name || p.email)}</td>
3492
+ <td>${escapeHtml(p.email)}</td>
3493
+ <td>${p.meetingCount}</td>
3494
+ <td>${formatDurationHtml(p.totalMinutes)}</td>
3495
+ </tr>`
3496
+ ).join("\n");
3497
+ }
3498
+ function renderDigestHtml(digest) {
3499
+ const actionItemsHtml = renderActionItemsHtml(digest);
3500
+ const meetingsHtml = renderMeetingsHtml(digest);
3501
+ const highlightsHtml = renderHighlightsHtml(digest);
3502
+ const participantsHtml = renderParticipantsHtml(digest);
3503
+ return `<!DOCTYPE html>
3504
+ <html lang="en">
3505
+ <head>
3506
+ <meta charset="UTF-8">
3507
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3508
+ <title>Weekly Meeting Digest</title>
3509
+ <style>
3510
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; }
3511
+ h1 { color: #333; border-bottom: 2px solid #4a90d9; padding-bottom: 10px; }
3512
+ h2 { color: #4a90d9; margin-top: 30px; }
3513
+ h3 { color: #666; }
3514
+ .overview { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; }
3515
+ .stat-card { background: #f5f5f5; padding: 20px; border-radius: 8px; text-align: center; }
3516
+ .stat-value { font-size: 2em; font-weight: bold; color: #4a90d9; }
3517
+ .stat-label { color: #666; }
3518
+ table { width: 100%; border-collapse: collapse; margin: 15px 0; }
3519
+ th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
3520
+ th { background: #f5f5f5; font-weight: 600; }
3521
+ .action-item { list-style: none; padding: 8px 0; border-bottom: 1px solid #eee; }
3522
+ .action-item input { margin-right: 10px; }
3523
+ .assignee { color: #4a90d9; margin-left: 10px; }
3524
+ .due-date { color: #e74c3c; margin-left: 10px; font-size: 0.9em; }
3525
+ .highlight { background: #f9f9f9; padding: 15px; border-radius: 8px; margin: 10px 0; }
3526
+ .highlight h4 { margin: 0 0 10px 0; color: #333; }
3527
+ .highlight ul { margin: 0; padding-left: 20px; }
3528
+ footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #999; font-size: 0.9em; }
3529
+ </style>
3530
+ </head>
3531
+ <body>
3532
+ <h1>Weekly Meeting Digest</h1>
3533
+ <p><strong>${escapeHtml(digest.period.from)}</strong> to <strong>${escapeHtml(digest.period.to)}</strong></p>
3534
+
3535
+ <div class="overview">
3536
+ <div class="stat-card">
3537
+ <div class="stat-value">${digest.totalMeetings}</div>
3538
+ <div class="stat-label">Meetings</div>
3539
+ </div>
3540
+ <div class="stat-card">
3541
+ <div class="stat-value">${formatDurationHtml(digest.totalDuration)}</div>
3542
+ <div class="stat-label">Total Time</div>
3543
+ </div>
3544
+ <div class="stat-card">
3545
+ <div class="stat-value">${digest.actionItems.total}</div>
3546
+ <div class="stat-label">Action Items</div>
3547
+ </div>
3548
+ </div>
3549
+
3550
+ <h2>Meeting Stats</h2>
3551
+ <p>Busiest day: <strong>${escapeHtml(digest.stats.busiestDay)}</strong></p>
3552
+ <p>Average duration: <strong>${formatDurationHtml(digest.stats.averageDuration)}</strong></p>
3553
+
3554
+ <h2>Meetings</h2>
3555
+ <table>
3556
+ <thead>
3557
+ <tr><th>Title</th><th>Date</th><th>Duration</th><th>Participants</th></tr>
3558
+ </thead>
3559
+ <tbody>
3560
+ ${meetingsHtml}
3561
+ </tbody>
3562
+ </table>
3563
+
3564
+ ${digest.actionItems.total > 0 ? `
3565
+ <h2>Action Items (${digest.actionItems.total})</h2>
3566
+ <ul style="padding-left: 0;">
3567
+ ${actionItemsHtml}
3568
+ </ul>` : ""}
3569
+
3570
+ ${digest.highlights.length > 0 ? `
3571
+ <h2>Highlights</h2>
3572
+ ${highlightsHtml}` : ""}
3573
+
3574
+ <h2>Participants</h2>
3575
+ <table>
3576
+ <thead>
3577
+ <tr><th>Name</th><th>Email</th><th>Meetings</th><th>Time</th></tr>
3578
+ </thead>
3579
+ <tbody>
3580
+ ${participantsHtml}
3581
+ </tbody>
3582
+ </table>
3583
+
3584
+ <footer>
3585
+ Generated with fireflies-api
3586
+ </footer>
3587
+ </body>
3588
+ </html>`;
3589
+ }
3590
+ var FILTERS = {
3591
+ duration: (value) => {
3592
+ const minutes = Math.round(Number(value) || 0);
3593
+ const hours = Math.floor(minutes / 60);
3594
+ const mins = minutes % 60;
3595
+ return `${hours}h ${mins}m`;
3596
+ },
3597
+ date: (value) => {
3598
+ const str = String(value || "");
3599
+ if (!str) return "";
3600
+ const date = new Date(str);
3601
+ if (Number.isNaN(date.getTime())) return str;
3602
+ return date.toLocaleDateString("en-US", {
3603
+ month: "short",
3604
+ day: "numeric",
3605
+ year: "numeric"
3606
+ });
3607
+ },
3608
+ join: (value) => {
3609
+ if (Array.isArray(value)) {
3610
+ return value.join(", ");
3611
+ }
3612
+ return String(value || "");
3613
+ },
3614
+ lowercase: (value) => String(value || "").toLowerCase(),
3615
+ uppercase: (value) => String(value || "").toUpperCase()
3616
+ };
3617
+ function renderTemplate(template, data) {
3618
+ if (!template) return "";
3619
+ let result = template;
3620
+ result = processSections(result, data);
3621
+ result = processVariables(result, data);
3622
+ return result;
3623
+ }
3624
+ function processSections(template, data) {
3625
+ const sectionPattern = /\{\{#(\w+(?:\.\w+)*)\}\}([\s\S]*?)\{\{\/\1\}\}/g;
3626
+ let result = template;
3627
+ let iterations = 0;
3628
+ const maxIterations = 100;
3629
+ for (; ; ) {
3630
+ const match = sectionPattern.exec(result);
3631
+ if (!match || iterations >= maxIterations) break;
3632
+ iterations++;
3633
+ const [fullMatch, path, content] = match;
3634
+ if (!path || content === void 0) continue;
3635
+ const value = getNestedValue(data, path);
3636
+ const replacement = renderSectionValue(value, content, data);
3637
+ result = result.slice(0, match.index) + replacement + result.slice(match.index + fullMatch.length);
3638
+ sectionPattern.lastIndex = 0;
3639
+ }
3640
+ return result;
3641
+ }
3642
+ function renderSectionValue(value, content, data) {
3643
+ if (Array.isArray(value)) {
3644
+ return renderArraySection(value, content, data);
3645
+ }
3646
+ if (isIterableObject(value)) {
3647
+ return renderObjectSection(value, content, data);
3648
+ }
3649
+ if (isTruthy(value)) {
3650
+ const processed = processSections(content, data);
3651
+ return processVariables(processed, data);
3652
+ }
3653
+ return "";
3654
+ }
3655
+ function renderArraySection(items, content, data) {
3656
+ let result = "";
3657
+ for (const item of items) {
3658
+ if (typeof item === "object" && item !== null) {
3659
+ const itemData = { ...data, ...item };
3660
+ const processed = processSections(content, itemData);
3661
+ result += processVariables(processed, itemData);
3662
+ } else {
3663
+ const itemContent = content.replace(/\{\{\.\}\}/g, String(item));
3664
+ result += processVariables(itemContent, data);
2610
3665
  }
2611
3666
  }
3667
+ return result;
2612
3668
  }
2613
- function delay(ms) {
2614
- return new Promise((resolve) => setTimeout(resolve, ms));
3669
+ function renderObjectSection(obj, content, data) {
3670
+ let result = "";
3671
+ for (const [key, itemValue] of Object.entries(obj)) {
3672
+ const itemData = buildObjectItemData(data, key, itemValue);
3673
+ const itemContent = content.replace(/\{\{\.\}\}/g, key);
3674
+ const processed = processSections(itemContent, itemData);
3675
+ result += processVariables(processed, itemData);
3676
+ }
3677
+ return result;
3678
+ }
3679
+ function buildObjectItemData(data, key, itemValue) {
3680
+ const baseData = { ...data, _key: key, _value: itemValue };
3681
+ if (typeof itemValue === "object" && itemValue !== null && !Array.isArray(itemValue)) {
3682
+ return { ...baseData, ...itemValue };
3683
+ }
3684
+ return baseData;
3685
+ }
3686
+ function isIterableObject(value) {
3687
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
3688
+ }
3689
+ function isTruthy(value) {
3690
+ if (value === null || value === void 0) return false;
3691
+ if (typeof value === "boolean") return value;
3692
+ if (typeof value === "string") return value.length > 0;
3693
+ if (typeof value === "number") return true;
3694
+ if (Array.isArray(value)) return value.length > 0;
3695
+ return true;
3696
+ }
3697
+ function processVariables(template, data) {
3698
+ return template.replace(
3699
+ /\{\{([\w.]+)(?:\s*\|\s*(\w+))?\}\}/g,
3700
+ (_match, path, filterName) => {
3701
+ const value = getNestedValue(data, path);
3702
+ if (value === void 0 || value === null) {
3703
+ return "";
3704
+ }
3705
+ if (filterName && FILTERS[filterName]) {
3706
+ return FILTERS[filterName](value);
3707
+ }
3708
+ return String(value);
3709
+ }
3710
+ );
3711
+ }
3712
+ function getNestedValue(obj, path) {
3713
+ const parts = path.split(".");
3714
+ let current = obj;
3715
+ for (const part of parts) {
3716
+ if (current === null || current === void 0) {
3717
+ return void 0;
3718
+ }
3719
+ if (typeof current !== "object") {
3720
+ return void 0;
3721
+ }
3722
+ current = current[part];
3723
+ }
3724
+ return current;
2615
3725
  }
3726
+ var DEFAULT_TEMPLATE = `# Weekly Meeting Digest
3727
+ **{{period.from}} to {{period.to}}**
3728
+
3729
+ ## Overview
3730
+ - **{{totalMeetings}}** meetings
3731
+ - **{{totalDuration | duration}}** total time
3732
+ - **{{actionItems.total}}** action items
3733
+
3734
+ ## Meeting Stats
3735
+ {{#stats.meetingsByDay}}
3736
+ - {{.}}
3737
+ {{/stats.meetingsByDay}}
3738
+
3739
+ Busiest day: {{stats.busiestDay}}
3740
+ Average duration: {{stats.averageDuration}} minutes
3741
+
3742
+ ## Action Items
3743
+ {{#actionItems.byAssignee}}
3744
+ ### {{.}}
3745
+ {{/actionItems.byAssignee}}
3746
+
3747
+ {{#actionItems.unassigned}}
3748
+ ### Unassigned
3749
+ - [ ] {{text}}
3750
+ {{/actionItems.unassigned}}
3751
+
3752
+ ## Highlights
3753
+ {{#highlights}}
3754
+ ### {{meetingTitle}} ({{meetingDate | date}})
3755
+ {{#keyPoints}}
3756
+ - {{.}}
3757
+ {{/keyPoints}}
3758
+ {{/highlights}}
3759
+
3760
+ ## Participants
3761
+ {{#participants}}
3762
+ - {{email}} ({{meetingCount}} meetings, {{totalMinutes | duration}})
3763
+ {{/participants}}
3764
+
3765
+ ---
3766
+ *Generated with fireflies-api*
3767
+ `;
3768
+ var COMPACT_TEMPLATE = `# Weekly Digest: {{period.from}} - {{period.to}}
3769
+
3770
+ **{{totalMeetings}}** meetings | **{{totalDuration | duration}}** | **{{actionItems.total}}** action items
3771
+
3772
+ {{#actionItems.unassigned}}
3773
+ ## Action Items
3774
+ - [ ] {{text}}
3775
+ {{/actionItems.unassigned}}
3776
+ `;
3777
+ var EXECUTIVE_TEMPLATE = `# Executive Summary
3778
+ **Weekly Meeting Report: {{period.from}} to {{period.to}}**
3779
+
3780
+ ## Key Metrics
3781
+ | Metric | Value |
3782
+ |--------|-------|
3783
+ | Total Meetings | {{totalMeetings}} |
3784
+ | Total Time | {{totalDuration | duration}} |
3785
+ | Action Items | {{actionItems.total}} |
3786
+ | Participants | {{participants.length}} |
3787
+
3788
+ ## Top Highlights
3789
+ {{#highlights}}
3790
+ - **{{meetingTitle}}**: {{#keyPoints}}{{.}} {{/keyPoints}}
3791
+ {{/highlights}}
3792
+
3793
+ ## Outstanding Action Items
3794
+ {{#actionItems.unassigned}}
3795
+ - {{text}}
3796
+ {{/actionItems.unassigned}}
3797
+
3798
+ ---
3799
+ *Executive Summary generated with fireflies-api*
3800
+ `;
2616
3801
 
2617
3802
  // src/helpers/external-questions.ts
2618
3803
  function findExternalParticipantQuestions(transcript, internalDomains) {
@@ -2687,227 +3872,6 @@ function isInternal(email, internalDomains) {
2687
3872
  return internalDomains.some((domain) => lowerEmail.endsWith(domain));
2688
3873
  }
2689
3874
 
2690
- // src/helpers/markdown.ts
2691
- var DEFAULT_OPTIONS2 = {
2692
- includeMetadata: true,
2693
- includeSummary: true,
2694
- includeActionItems: true,
2695
- actionItemFormat: "checkbox",
2696
- includeTimestamps: false,
2697
- speakerFormat: "bold",
2698
- groupBySpeaker: true
2699
- };
2700
- var DEFAULT_CHUNKS_OPTIONS = {
2701
- title: "Live Transcript",
2702
- includeTimestamps: false,
2703
- speakerFormat: "bold",
2704
- groupBySpeaker: true
2705
- };
2706
- async function transcriptToMarkdown(transcript, options = {}) {
2707
- const opts = { ...DEFAULT_OPTIONS2, ...options };
2708
- const sections = [];
2709
- if (opts.includeMetadata) {
2710
- sections.push(formatMetadata(transcript));
2711
- }
2712
- if (opts.includeSummary && transcript.summary) {
2713
- sections.push(formatSummary(transcript.summary, opts));
2714
- }
2715
- if (transcript.sentences && transcript.sentences.length > 0) {
2716
- sections.push(formatTranscript(transcript.sentences, opts));
2717
- }
2718
- const content = sections.join("\n\n---\n\n");
2719
- await writeIfOutputPath(content, options.outputPath);
2720
- return content;
2721
- }
2722
- async function chunksToMarkdown(chunks, options = {}) {
2723
- const opts = { ...DEFAULT_CHUNKS_OPTIONS, ...options };
2724
- const lines = [`# ${opts.title}`];
2725
- if (chunks.length === 0) {
2726
- lines.push("", "## Transcript", "", "*No transcription data*");
2727
- } else {
2728
- lines.push("", "## Transcript");
2729
- if (opts.groupBySpeaker) {
2730
- const groups = groupChunksBySpeaker(chunks);
2731
- for (const group of groups) {
2732
- lines.push("", formatChunkGroup(group, opts));
2733
- }
2734
- } else {
2735
- for (const chunk of chunks) {
2736
- lines.push("", formatChunk(chunk, opts));
2737
- }
2738
- }
2739
- }
2740
- const content = lines.join("\n");
2741
- await writeIfOutputPath(content, options.outputPath);
2742
- return content;
2743
- }
2744
- function formatMetadata(transcript) {
2745
- const lines = [`# ${transcript.title || "Untitled Meeting"}`];
2746
- if (transcript.dateString) {
2747
- lines.push(`
2748
- **Date:** ${formatDate(transcript.dateString)}`);
2749
- }
2750
- const duration = calculateDuration(transcript);
2751
- if (duration > 0) {
2752
- lines.push(`**Duration:** ${formatDuration(duration)}`);
2753
- }
2754
- const participants = getParticipantNames(transcript);
2755
- if (participants.length > 0) {
2756
- lines.push(`**Participants:** ${participants.join(", ")}`);
2757
- }
2758
- return lines.join("\n");
2759
- }
2760
- function calculateDuration(transcript) {
2761
- if (transcript.sentences && transcript.sentences.length > 0) {
2762
- const lastSentence = transcript.sentences[transcript.sentences.length - 1];
2763
- if (lastSentence) {
2764
- return parseFloat(lastSentence.end_time);
2765
- }
2766
- }
2767
- return transcript.duration || 0;
2768
- }
2769
- function formatSummary(summary, opts) {
2770
- const sections = ["## Summary"];
2771
- if (summary.gist) {
2772
- sections.push("", summary.gist);
2773
- }
2774
- if (summary.bullet_gist) {
2775
- const bullets = parseMultilineField(summary.bullet_gist);
2776
- if (bullets.length > 0) {
2777
- sections.push("", "### Key Points");
2778
- sections.push(bullets.map((p) => `- ${p}`).join("\n"));
2779
- }
2780
- }
2781
- if (opts.includeActionItems && summary.action_items) {
2782
- const items = parseMultilineField(summary.action_items);
2783
- if (items.length > 0) {
2784
- sections.push("", "### Action Items");
2785
- const prefix = opts.actionItemFormat === "checkbox" ? "- [ ] " : "- ";
2786
- sections.push(items.map((a) => `${prefix}${a}`).join("\n"));
2787
- }
2788
- }
2789
- return sections.join("\n");
2790
- }
2791
- function formatTranscript(sentences, opts) {
2792
- const lines = ["## Transcript"];
2793
- if (opts.groupBySpeaker) {
2794
- const groups = groupSentencesBySpeaker(sentences);
2795
- for (const group of groups) {
2796
- lines.push("", formatSpeakerGroup(group, opts));
2797
- }
2798
- } else {
2799
- for (const sentence of sentences) {
2800
- lines.push("", formatSentence(sentence, opts));
2801
- }
2802
- }
2803
- return lines.join("\n");
2804
- }
2805
- function groupSentencesBySpeaker(sentences) {
2806
- const groups = [];
2807
- let current = null;
2808
- for (const sentence of sentences) {
2809
- if (!current || current.speakerName !== sentence.speaker_name) {
2810
- current = { speakerName: sentence.speaker_name, sentences: [] };
2811
- groups.push(current);
2812
- }
2813
- current.sentences.push(sentence);
2814
- }
2815
- return groups;
2816
- }
2817
- function formatSpeakerGroup(group, opts) {
2818
- const speaker = formatSpeakerName(group.speakerName, opts.speakerFormat);
2819
- const text = group.sentences.map((s) => s.text).join(" ");
2820
- const firstSentence = group.sentences[0];
2821
- if (opts.includeTimestamps && firstSentence) {
2822
- const timestamp = formatTimestamp(firstSentence.start_time);
2823
- return `${timestamp} ${speaker} ${text}`;
2824
- }
2825
- return `${speaker} ${text}`;
2826
- }
2827
- function formatSentence(sentence, opts) {
2828
- const speaker = formatSpeakerName(sentence.speaker_name, opts.speakerFormat);
2829
- if (opts.includeTimestamps) {
2830
- const timestamp = formatTimestamp(sentence.start_time);
2831
- return `${timestamp} ${speaker} ${sentence.text}`;
2832
- }
2833
- return `${speaker} ${sentence.text}`;
2834
- }
2835
- function groupChunksBySpeaker(chunks) {
2836
- const groups = [];
2837
- let current = null;
2838
- for (const chunk of chunks) {
2839
- if (!current || current.speakerName !== chunk.speaker_name) {
2840
- current = { speakerName: chunk.speaker_name, chunks: [] };
2841
- groups.push(current);
2842
- }
2843
- current.chunks.push(chunk);
2844
- }
2845
- return groups;
2846
- }
2847
- function formatChunkGroup(group, opts) {
2848
- const speaker = formatSpeakerName(group.speakerName, opts.speakerFormat);
2849
- const text = group.chunks.map((c) => c.text).join(" ");
2850
- const firstChunk = group.chunks[0];
2851
- if (opts.includeTimestamps && firstChunk) {
2852
- const timestamp = formatTimestamp(firstChunk.start_time.toString());
2853
- return `${timestamp} ${speaker} ${text}`;
2854
- }
2855
- return `${speaker} ${text}`;
2856
- }
2857
- function formatChunk(chunk, opts) {
2858
- const speaker = formatSpeakerName(chunk.speaker_name, opts.speakerFormat);
2859
- if (opts.includeTimestamps) {
2860
- const timestamp = formatTimestamp(chunk.start_time.toString());
2861
- return `${timestamp} ${speaker} ${chunk.text}`;
2862
- }
2863
- return `${speaker} ${chunk.text}`;
2864
- }
2865
- function formatSpeakerName(name, format) {
2866
- switch (format) {
2867
- case "bold":
2868
- return `**${name}:**`;
2869
- case "plain":
2870
- return `${name}:`;
2871
- }
2872
- }
2873
- function formatTimestamp(startTime) {
2874
- const seconds = parseFloat(startTime);
2875
- const mins = Math.floor(seconds / 60);
2876
- const secs = Math.floor(seconds % 60);
2877
- return `[${mins}:${secs.toString().padStart(2, "0")}]`;
2878
- }
2879
- function formatDuration(seconds) {
2880
- const hours = Math.floor(seconds / 3600);
2881
- const mins = Math.floor(seconds % 3600 / 60);
2882
- if (hours > 0) {
2883
- return `${hours}h ${mins}m`;
2884
- }
2885
- return `${mins} minutes`;
2886
- }
2887
- function formatDate(isoString) {
2888
- return new Date(isoString).toLocaleDateString("en-US", {
2889
- weekday: "long",
2890
- year: "numeric",
2891
- month: "long",
2892
- day: "numeric"
2893
- });
2894
- }
2895
- function getParticipantNames(transcript) {
2896
- if (transcript.meeting_attendees?.length) {
2897
- return transcript.meeting_attendees.map((a) => a.displayName || a.name || a.email).filter(Boolean);
2898
- }
2899
- return transcript.speakers?.map((s) => s.name) || [];
2900
- }
2901
- function parseMultilineField(value) {
2902
- return value.split(/\n/).map((line) => line.trim()).filter((line) => line.length > 0);
2903
- }
2904
- async function writeIfOutputPath(content, outputPath) {
2905
- if (outputPath) {
2906
- const { writeFile } = await import('fs/promises');
2907
- await writeFile(outputPath, content, "utf-8");
2908
- }
2909
- }
2910
-
2911
3875
  // src/utils/dedup.ts
2912
3876
  var Deduplicator = class {
2913
3877
  seen = /* @__PURE__ */ new Set();
@@ -3339,6 +4303,6 @@ function parseWebhookPayload(payload, options) {
3339
4303
  return payload;
3340
4304
  }
3341
4305
 
3342
- export { AuthenticationError, ChunkTimeoutError, ConnectionError, Deduplicator, FirefliesClient, FirefliesError, GraphQLError, NetworkError, NotFoundError, RateLimitError, RealtimeError, RealtimeStream, StreamClosedError, TimeoutError, TranscriptAccumulator, ValidationError, WebhookParseError, WebhookVerificationError, aggregateActionItems, analyzeMeetings, analyzeSpeakers, batch, batchAll, chunksToMarkdown, collectAll, createNormalizer, extractActionItems, extractDomain, filterActionItems, findExternalParticipantQuestions, formatActionItemsMarkdown, getMeetingVideos, getMeetingsForMultipleUsers, hasExternalParticipants, hasVideo, isValidWebhookPayload, normalizeTranscript2 as normalizeTranscript, normalizeTranscripts, normalizeTranscriptsAll, paginate, parseWebhookPayload, searchTranscript, transcriptToMarkdown, verifyWebhookSignature };
4306
+ export { AuthenticationError, ChunkTimeoutError, ConnectionError, Deduplicator, FirefliesClient, FirefliesError, GraphQLError, NetworkError, NotFoundError, RateLimitError, RealtimeError, RealtimeStream, StreamClosedError, TimeoutError, TranscriptAccumulator, ValidationError, WebhookParseError, WebhookVerificationError, aggregateActionItems, aggregateActionItemsForDigest, aggregateParticipants2 as aggregateParticipants, analyzeMeetings, analyzeSpeakers, batch, batchAll, buildDigest, calculateStats, chunksToMarkdown, collectAll, createNormalizer, createZipArchive, exportTranscript, extractActionItems, extractDomain, extractHighlights, filterActionItems, findExternalParticipantQuestions, formatActionItemsMarkdown, generateExportFilename, getMeetingVideos, getMeetingsForMultipleUsers, hasExternalParticipants, hasVideo, isValidWebhookPayload, normalizeTranscript2 as normalizeTranscript, normalizeTranscripts, normalizeTranscriptsAll, paginate, paginateParallel, parseWebhookPayload, renderDigest, renderDigestHtml, renderTemplate, sanitizeFilename, searchTranscript, transcriptToCsv, transcriptToMarkdown, transcriptToText, verifyWebhookSignature };
3343
4307
  //# sourceMappingURL=index.js.map
3344
4308
  //# sourceMappingURL=index.js.map