code icon Code

List Fireflies Meetings

Fetch recent meetings with metadata from Fireflies.ai

Source Code

import fs from "fs";
import path from "path";

const [outputPath, limit, keyword, fromDate] = process.argv.slice(2);

if (!outputPath) {
  console.error("Error: outputPath is required");
  console.error("Usage: node script.js session/meetings.json [limit] [keyword] [fromDate]");
  process.exit(1);
}

const limitNum = limit ? Math.min(parseInt(limit, 10) || 20, 50) : 20;

console.log("Fetching meetings from Fireflies...");
if (keyword) console.log(`  Search: "${keyword}"`);
if (fromDate) console.log(`  From: ${fromDate}`);

try {
  // Build GraphQL query
  const query = `
    query Transcripts($limit: Int, $keyword: String, $fromDate: DateTime) {
      transcripts(limit: $limit, keyword: $keyword, fromDate: $fromDate) {
        id
        title
        date
        duration
        participants
        organizer_email
        transcript_url
        speakers {
          id
          name
        }
        summary {
          gist
          short_summary
          action_items
        }
      }
    }
  `;

  const variables = { limit: limitNum };
  if (keyword) variables.keyword = keyword;
  if (fromDate) variables.fromDate = fromDate;

  const res = await fetch("https://api.fireflies.ai/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer PLACEHOLDER_TOKEN",
    },
    body: JSON.stringify({ query, variables }),
  });

  const data = await res.json();

  if (data.errors) {
    const errMsg = data.errors[0]?.message || "GraphQL error";
    throw new Error(`Fireflies API: ${errMsg}`);
  }

  const transcripts = data.data?.transcripts || [];
  console.log(`  Found ${transcripts.length} meetings`);

  // Process meetings for readable output
  const meetings = transcripts.map((t) => {
    // Convert epoch ms to ISO string
    const dateISO = t.date ? new Date(t.date).toISOString() : null;
    const dateFormatted = t.date
      ? new Date(t.date).toLocaleDateString("en-US", {
          weekday: "short",
          month: "short",
          day: "numeric",
        })
      : null;

    return {
      id: t.id,
      title: t.title || "(Untitled meeting)",
      date: dateISO,
      dateFormatted,
      durationMinutes: t.duration || null,
      participants: t.participants || [],
      participantCount: (t.participants || []).length,
      organizer: t.organizer_email || null,
      speakers: (t.speakers || []).map((s) => s.name).filter(Boolean),
      speakerCount: (t.speakers || []).length,
      gist: t.summary?.gist || null,
      shortSummary: t.summary?.short_summary || null,
      actionItems: t.summary?.action_items || null,
      transcriptUrl: t.transcript_url || null,
    };
  });

  // Sort by date (newest first)
  meetings.sort((a, b) => new Date(b.date) - new Date(a.date));

  // Build output
  const output = {
    query: {
      limit: limitNum,
      keyword: keyword || null,
      fromDate: fromDate || null,
    },
    summary: {
      totalMeetings: meetings.length,
    },
    meetings,
  };

  // Write output
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  console.log(`\nāœ“ Found ${meetings.length} meetings`);
  console.log(`  Output: ${outputPath}`);

  // Show preview
  if (meetings.length > 0) {
    console.log(`\n  Recent meetings:`);
    meetings.slice(0, 5).forEach((m) => {
      const duration = m.durationMinutes ? `${m.durationMinutes} min` : "";
      const speakers = m.speakerCount > 0 ? `${m.speakerCount} speakers` : "";
      const meta = [duration, speakers].filter(Boolean).join(", ");
      console.log(`    ${m.dateFormatted} - ${m.title}${meta ? ` (${meta})` : ""}`);
    });
    if (meetings.length > 5) {
      console.log(`    ... and ${meetings.length - 5} more`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      meetingCount: meetings.length,
    })
  );
} catch (error) {
  console.error("Failed:", error.message);
  throw error;
}