diff --git a/plugins/core/skills/fireflies/SKILL.md b/plugins/core/skills/fireflies/SKILL.md
new file mode 100644
index 0000000..343d56b
--- /dev/null
+++ b/plugins/core/skills/fireflies/SKILL.md
@@ -0,0 +1,85 @@
+---
+name: fireflies
+# prettier-ignore
+description: "Use when finding meeting transcripts, searching Fireflies recordings, getting action items from calls, or answering 'what was discussed in the meeting' questions"
+version: 1.0.0
+category: research
+triggers:
+ - "fireflies"
+ - "meeting transcript"
+ - "meeting notes"
+ - "what was discussed"
+ - "action items"
+ - "zoom call"
+ - "teams meeting"
+ - "google meet"
+---
+
+
+Query Fireflies.ai meeting transcripts - recorded calls with AI-generated summaries, action items, and searchable conversation history. Transform "what happened in that meeting?" into structured, actionable insights.
+
+
+
+Use when finding meeting content, extracting action items, searching professional discussions, getting context from recorded calls, or building understanding from past meetings.
+
+Clear triggers:
+- "What meetings did I have today/this week?"
+- "What was discussed in the [project] meeting?"
+- "What were the action items from yesterday's call?"
+- "Find meetings about [topic]"
+
+
+
+Set `FIREFLIES_API_KEY` environment variable. Get your key from [app.fireflies.ai](https://app.fireflies.ai) → Integrations → Fireflies API.
+
+
+
+```bash
+# Recent transcripts (default: 5)
+fireflies recent
+fireflies recent 10
+
+# Today's meetings
+fireflies today
+
+# Specific date
+fireflies date 2026-01-28
+
+# Search by keyword
+fireflies search "product roadmap"
+fireflies search "budget discussion"
+
+# Full transcript by ID
+fireflies get abc123xyz
+
+# Your account info
+fireflies me
+```
+
+
+
+**List view includes:**
+- id, title, duration, host, participants
+- AI-generated overview and action items
+
+**Full transcript includes:**
+- Complete sentences with speaker names and timestamps
+- Keywords, topics discussed, outline
+- Extracted action items
+
+
+
+- Works with Zoom, Google Meet, Microsoft Teams
+- Speaker names from calendar invites
+- GraphQL API docs: [docs.fireflies.ai](https://docs.fireflies.ai)
+
+
+
+If you need to look up API details beyond this skill's commands, use Context7:
+```
+resolve-library-id: fireflies → /websites/fireflies_ai
+query-docs: /websites/fireflies_ai with "GraphQL transcripts query"
+```
+
+Context7 has full GraphQL schema documentation with code examples in Python, JavaScript, and cURL.
+
diff --git a/plugins/core/skills/fireflies/scripts/fireflies b/plugins/core/skills/fireflies/scripts/fireflies
new file mode 100755
index 0000000..e8ff94f
--- /dev/null
+++ b/plugins/core/skills/fireflies/scripts/fireflies
@@ -0,0 +1,454 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = ["httpx", "rich", "python-dotenv"]
+# ///
+"""
+Fireflies.ai CLI - Query your meeting transcripts.
+
+Requires FIREFLIES_API_KEY environment variable.
+Get your key from: https://app.fireflies.ai → Integrations → Fireflies API
+"""
+
+import json
+import os
+import sys
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+
+import httpx
+from dotenv import load_dotenv
+from rich.console import Console
+from rich.markdown import Markdown
+from rich.panel import Panel
+
+console = Console()
+
+# Load .env files in priority order (later overrides earlier)
+ENV_LOCATIONS = [
+ Path.home() / ".env",
+ Path.home() / ".clawdbot" / "clawdbot.json",
+ Path.cwd() / ".env",
+ Path.cwd() / ".env.local",
+]
+
+
+def load_api_key() -> str | None:
+ """Load API key from environment or .env files.
+
+ Priority (later overrides earlier):
+ 1. ~/.env
+ 2. ~/.clawdbot/clawdbot.json
+ 3. ./.env
+ 4. ./.env.local
+ 5. Environment variable (highest priority)
+ """
+ for env_path in ENV_LOCATIONS:
+ if env_path.exists():
+ if env_path.suffix == ".json":
+ try:
+ config = json.loads(env_path.read_text())
+ env_vars = config.get("env", {})
+ if "FIREFLIES_API_KEY" in env_vars:
+ os.environ["FIREFLIES_API_KEY"] = env_vars["FIREFLIES_API_KEY"]
+ except (json.JSONDecodeError, KeyError) as e:
+ console.print(f"[yellow]Warning: Failed to parse {env_path}: {e}[/yellow]", file=sys.stderr)
+ else:
+ load_dotenv(env_path, override=True)
+
+ return os.environ.get("FIREFLIES_API_KEY")
+
+
+def show_setup_instructions() -> None:
+ """Display friendly setup instructions when API key is missing."""
+ instructions = """
+[bold red]API Key Required[/bold red]
+
+To use the Fireflies CLI, you need an API key from your Fireflies.ai account.
+
+[bold cyan]Step 1: Get your API key[/bold cyan]
+1. Go to [link=https://app.fireflies.ai]app.fireflies.ai[/link]
+2. Click Integrations → Fireflies API
+3. Generate or copy your API key
+
+[bold cyan]Step 2: Set your API key[/bold cyan]
+
+Option A: Environment variable (recommended for temporary use)
+[dim]export FIREFLIES_API_KEY=your-api-key-here[/dim]
+
+Option B: Add to ~/.env (recommended for persistent use)
+[dim]echo "FIREFLIES_API_KEY=your-api-key-here" >> ~/.env[/dim]
+
+Option C: Add to project .env or .env.local
+[dim]echo "FIREFLIES_API_KEY=your-api-key-here" >> .env[/dim]
+
+[bold cyan]Searched locations:[/bold cyan]
+"""
+ console.print(Panel(instructions, title="🔥 Fireflies Setup", border_style="yellow"))
+
+ for loc in ENV_LOCATIONS:
+ status = "✓" if loc.exists() else "✗"
+ color = "green" if loc.exists() else "dim"
+ console.print(f" [{color}]{status} {loc}[/{color}]")
+
+ console.print()
+
+
+API_KEY = load_api_key()
+API_URL = "https://api.fireflies.ai/graphql"
+
+# GraphQL Queries
+LIST_QUERY = """
+query($limit: Int, $skip: Int, $keyword: String, $scope: String, $fromDate: DateTime, $toDate: DateTime, $mine: Boolean) {
+ transcripts(limit: $limit, skip: $skip, keyword: $keyword, scope: $scope, fromDate: $fromDate, toDate: $toDate, mine: $mine) {
+ id
+ title
+ date
+ duration
+ host_email
+ participants
+ organizer_email
+ summary {
+ overview
+ short_summary
+ keywords
+ action_items
+ }
+ }
+}
+"""
+
+FULL_QUERY = """
+query($id: String!) {
+ transcript(id: $id) {
+ id
+ title
+ date
+ duration
+ host_email
+ participants
+ organizer_email
+ sentences {
+ speaker_name
+ text
+ start_time
+ end_time
+ }
+ summary {
+ keywords
+ action_items
+ outline
+ overview
+ bullet_gist
+ short_summary
+ topics_discussed
+ }
+ }
+}
+"""
+
+USER_QUERY = """
+query {
+ user {
+ user_id
+ email
+ name
+ num_transcripts
+ minutes_consumed
+ }
+}
+"""
+
+
+def error(msg: str) -> None:
+ console.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
+
+
+def graphql(query: str, variables: dict | None = None) -> dict | None:
+ """Execute GraphQL query against Fireflies API."""
+ if not API_KEY:
+ show_setup_instructions()
+ sys.exit(1)
+
+ try:
+ response = httpx.post(
+ API_URL,
+ json={"query": query, "variables": variables or {}},
+ headers={
+ "Content-Type": "application/json",
+ "Authorization": f"Bearer {API_KEY}",
+ },
+ timeout=30.0,
+ )
+ response.raise_for_status()
+ result = response.json()
+ # Check for GraphQL errors in response
+ if "errors" in result:
+ error(f"GraphQL error: {result['errors'][0].get('message', 'Unknown error')}")
+ return None
+ return result
+ except httpx.HTTPStatusError as e:
+ if e.response.status_code == 401:
+ error("Invalid API key. Please check your FIREFLIES_API_KEY.")
+ console.print("[dim]Run 'fireflies help' for setup instructions.[/dim]")
+ elif e.response.status_code == 429:
+ error("Rate limit exceeded. Please wait before retrying.")
+ else:
+ error(f"API error: {e.response.status_code} - {e.response.text}")
+ return None
+ except httpx.RequestError as e:
+ error(f"Request failed: {e}")
+ return None
+
+
+def format_list(data: dict) -> None:
+ """Format and display transcript list as markdown."""
+ transcripts = data.get("data", {}).get("transcripts", [])
+ if not transcripts:
+ console.print("[yellow]No transcripts found[/yellow]")
+ return
+
+ for t in transcripts:
+ title = t.get("title") or "Untitled"
+ tid = t.get("id", "")
+ duration = (t.get("duration") or 0) // 60
+ host = t.get("host_email") or "unknown"
+ participants = ", ".join(t.get("participants") or [])
+ summary = t.get("summary") or {}
+ overview = summary.get("overview") or summary.get("short_summary") or "No summary available"
+ action_items = summary.get("action_items") or "None"
+
+ output = f"""## {title}
+**ID:** {tid}
+**Duration:** {duration}m
+**Host:** {host}
+**Participants:** {participants}
+
+### Summary
+{overview}
+
+### Action Items
+{action_items}
+
+---
+"""
+ console.print(Markdown(output))
+
+
+def format_transcript(data: dict) -> None:
+ """Format and display full transcript as markdown."""
+ t = data.get("data", {}).get("transcript")
+ if not t:
+ console.print("[yellow]Transcript not found[/yellow]")
+ return
+
+ title = t.get("title") or "Untitled"
+ duration = (t.get("duration") or 0) // 60
+ host = t.get("host_email") or "unknown"
+ participants = ", ".join(t.get("participants") or [])
+ summary = t.get("summary") or {}
+ overview = summary.get("overview") or summary.get("short_summary") or "No summary"
+ keywords = ", ".join(summary.get("keywords") or [])
+ action_items = summary.get("action_items") or "None"
+ topics = ", ".join(summary.get("topics_discussed") or [])
+
+ sentences = t.get("sentences") or []
+ transcript_text = "\n".join(
+ f"{s.get('speaker_name', 'Speaker')}: {s.get('text', '')}" for s in sentences
+ )
+
+ output = f"""# {title}
+**Duration:** {duration} minutes
+**Host:** {host}
+**Participants:** {participants}
+
+## Summary
+{overview}
+
+## Keywords
+{keywords}
+
+## Action Items
+{action_items}
+
+## Topics Discussed
+{topics}
+
+## Transcript
+{transcript_text}
+"""
+ console.print(Markdown(output))
+
+
+def format_user(data: dict) -> None:
+ """Format and display user info."""
+ user = data.get("data", {}).get("user")
+ if not user:
+ console.print("[yellow]User info not found[/yellow]")
+ return
+
+ output = f"""**User:** {user.get('name')} ({user.get('email')})
+**ID:** {user.get('user_id')}
+**Total Transcripts:** {user.get('num_transcripts')}
+**Minutes Used:** {user.get('minutes_consumed')}
+"""
+ console.print(output)
+
+
+def cmd_recent(limit: int = 5) -> None:
+ """Get N most recent transcripts."""
+ data = graphql(LIST_QUERY, {"limit": limit, "mine": True})
+ if data:
+ format_list(data)
+
+
+def cmd_today() -> None:
+ """Get today's meetings."""
+ today = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
+ tomorrow = today + timedelta(days=1)
+ data = graphql(
+ LIST_QUERY,
+ {
+ "fromDate": today.isoformat().replace("+00:00", ".000Z"),
+ "toDate": tomorrow.isoformat().replace("+00:00", ".000Z"),
+ "limit": 20,
+ "mine": True,
+ },
+ )
+ if data:
+ format_list(data)
+
+
+def cmd_date(date_str: str) -> None:
+ """Get meetings for a specific date."""
+ # Validate date format
+ try:
+ datetime.strptime(date_str, "%Y-%m-%d")
+ except ValueError:
+ error(f"Invalid date format: '{date_str}'. Use YYYY-MM-DD (e.g., 2026-01-28)")
+ sys.exit(1)
+
+ from_date = f"{date_str}T00:00:00.000Z"
+ to_date = f"{date_str}T23:59:59.999Z"
+ data = graphql(
+ LIST_QUERY,
+ {"fromDate": from_date, "toDate": to_date, "limit": 20, "mine": True},
+ )
+ if data:
+ format_list(data)
+
+
+def cmd_search(keyword: str) -> None:
+ """Search transcripts by keyword."""
+ data = graphql(
+ LIST_QUERY,
+ {"keyword": keyword, "scope": "all", "limit": 10, "mine": True},
+ )
+ if data:
+ format_list(data)
+
+
+def cmd_get(transcript_id: str) -> None:
+ """Get full transcript by ID."""
+ data = graphql(FULL_QUERY, {"id": transcript_id})
+ if data:
+ format_transcript(data)
+
+
+def cmd_me() -> None:
+ """Get user info."""
+ data = graphql(USER_QUERY)
+ if data:
+ format_user(data)
+
+
+def cmd_raw(query: str, variables: str = "{}") -> None:
+ """Raw GraphQL query."""
+ try:
+ vars_dict = json.loads(variables)
+ except json.JSONDecodeError:
+ error("Invalid JSON for variables")
+ sys.exit(1)
+
+ data = graphql(query, vars_dict)
+ if data:
+ console.print_json(data=data)
+
+
+def show_help() -> None:
+ """Display help message."""
+ help_text = """
+# Fireflies CLI - Query your meeting transcripts
+
+## Commands
+- `recent [N]` - Get N most recent transcripts (default: 5)
+- `today` - Get today's meetings
+- `date YYYY-MM-DD` - Get meetings for a specific date
+- `search "query"` - Search transcripts by keyword
+- `get ` - Get full transcript by ID
+- `me` - Get your user info
+- `raw [vars]` - Raw GraphQL query
+
+## Environment
+- `FIREFLIES_API_KEY` - Required - your API key
+
+## Examples
+```
+fireflies recent 3
+fireflies today
+fireflies date 2026-01-28
+fireflies search "product roadmap"
+fireflies get abc123xyz
+fireflies me
+```
+"""
+ console.print(Markdown(help_text))
+
+ if not API_KEY:
+ console.print()
+ show_setup_instructions()
+
+
+def main() -> None:
+ args = sys.argv[1:]
+ cmd = args[0] if args else "help"
+
+ match cmd:
+ case "recent":
+ try:
+ limit = int(args[1]) if len(args) > 1 else 5
+ except ValueError:
+ error(f"Invalid limit: '{args[1]}'. Must be a number.")
+ sys.exit(1)
+ cmd_recent(limit)
+ case "today":
+ cmd_today()
+ case "date":
+ if len(args) < 2:
+ error("Usage: fireflies date YYYY-MM-DD")
+ sys.exit(1)
+ cmd_date(args[1])
+ case "search":
+ if len(args) < 2:
+ error('Usage: fireflies search "query"')
+ sys.exit(1)
+ cmd_search(args[1])
+ case "get":
+ if len(args) < 2:
+ error("Usage: fireflies get ")
+ sys.exit(1)
+ cmd_get(args[1])
+ case "me":
+ cmd_me()
+ case "raw":
+ if len(args) < 2:
+ error("Usage: fireflies raw [variables_json]")
+ sys.exit(1)
+ variables = args[2] if len(args) > 2 else "{}"
+ cmd_raw(args[1], variables)
+ case "help" | "-h" | "--help" | _:
+ show_help()
+
+
+if __name__ == "__main__":
+ main()