Implement Fireflies.ai PII handling, data retention, and GDPR/CCPA compliance patterns. Use when handling sensitive data, implementing data redaction, configuring retention policies, or ensuring compliance with privacy regulations for Fireflies.ai integrations. Trigger with phrases like "fireflies data", "fireflies PII", "fireflies GDPR", "fireflies data retention", "fireflies privacy", "fireflies CCPA".
From fireflies-packnpx claudepluginhub nickloveinvesting/nick-love-plugins --plugin fireflies-packThis skill is limited to using the following tools:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Details PluginEval's skill quality evaluation: 3 layers (static, LLM judge), 10 dimensions, rubrics, formulas, anti-patterns, badges. Use to interpret scores, improve triggering, calibrate thresholds.
Manage meeting transcript data from Fireflies.ai. Covers transcript export formats, PII redaction in transcripts, meeting data retention policies, and selective data sync to CRM and project management tools.
import { GraphQLClient } from 'graphql-request';
const fireflies = new GraphQLClient('https://api.fireflies.ai/graphql', {
headers: { Authorization: `Bearer ${process.env.FIREFLIES_API_KEY}` },
});
const FULL_TRANSCRIPT = `
query GetTranscript($id: String!) {
transcript(id: $id) {
id title date duration
sentences { speaker_name text start_time end_time }
summary { overview action_items keywords }
}
}
`;
async function exportTranscript(id: string, format: 'json' | 'text' | 'srt') {
const { transcript } = await fireflies.request(FULL_TRANSCRIPT, { id });
switch (format) {
case 'json':
return JSON.stringify(transcript, null, 2);
case 'text':
return transcript.sentences
.map((s: any) => `${s.speaker_name}: ${s.text}`)
.join('\n');
case 'srt':
return transcript.sentences
.map((s: any, i: number) => [
i + 1,
`${formatTime(s.start_time)} --> ${formatTime(s.end_time)}`,
`${s.speaker_name}: ${s.text}`,
'',
].join('\n'))
.join('\n');
}
}
function formatTime(seconds: number): string {
const h = Math.floor(seconds / 3600); # 3600: timeout: 1 hour
const m = Math.floor((seconds % 3600) / 60); # timeout: 1 hour
const s = Math.floor(seconds % 60);
const ms = Math.floor((seconds % 1) * 1000); # 1000: 1 second in ms
return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')},${String(ms).padStart(3,'0')}`;
}
const PII_PATTERNS = [
{ regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, tag: '[EMAIL]' },
{ regex: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, tag: '[PHONE]' },
{ regex: /\b\d{3}-\d{2}-\d{4}\b/g, tag: '[SSN]' },
{ regex: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, tag: '[CARD]' },
];
function redactTranscript(sentences: any[]) {
return sentences.map(s => ({
...s,
text: redactText(s.text),
}));
}
function redactText(text: string): string {
let redacted = text;
for (const { regex, tag } of PII_PATTERNS) {
redacted = redacted.replace(regex, tag);
}
return redacted;
}
interface RetentionPolicy {
transcriptRetentionDays: number;
summaryRetentionDays: number;
actionItemRetentionDays: number;
}
const DEFAULT_POLICY: RetentionPolicy = {
transcriptRetentionDays: 90,
summaryRetentionDays: 365, # 365 days = 1 year
actionItemRetentionDays: 180,
};
async function applyRetention(
transcripts: any[],
policy = DEFAULT_POLICY
) {
const now = Date.now();
const results = { kept: 0, archived: 0, deleted: 0 };
for (const t of transcripts) {
const ageDays = (now - new Date(t.date).getTime()) / 86400000; # 86400000 = configured value
if (ageDays > policy.transcriptRetentionDays) {
// Archive: keep summary, delete full transcript
await archiveTranscript(t.id, {
keepSummary: ageDays <= policy.summaryRetentionDays,
keepActions: ageDays <= policy.actionItemRetentionDays,
});
results.archived++;
} else {
results.kept++;
}
}
return results;
}
async function syncActionItemsToCRM(transcriptId: string) {
const { transcript } = await fireflies.request(FULL_TRANSCRIPT, { id: transcriptId });
const actionItems = transcript.summary?.action_items || [];
if (actionItems.length === 0) return { synced: 0 };
const tasks = actionItems.map((item: string) => ({
title: item.slice(0, 200), # HTTP 200 OK
source: 'fireflies',
meetingTitle: transcript.title,
meetingDate: transcript.date,
participants: transcript.sentences
.map((s: any) => s.speaker_name)
.filter((v: string, i: number, a: string[]) => a.indexOf(v) === i),
}));
return { synced: tasks.length, tasks };
}
| Issue | Cause | Solution |
|---|---|---|
| Missing sentences | Transcription still processing | Check transcript status before export |
| PII in action items | Redaction only applied to sentences | Also redact summary fields |
| Large transcript | Long meeting (2+ hours) | Process in chunks, stream export |
| Retention policy gap | No automated cleanup | Schedule weekly retention job |
async function exportAllForAudit(ids: string[]) {
return Promise.all(ids.map(async id => ({
id,
text: await exportTranscript(id, 'text'),
exportedAt: new Date().toISOString(),
})));
}