Claude Skill

youtube

Work with YouTube content end to end — fetch transcripts and turn them into summaries, blog posts, social content, quotes, or show notes; create high-CTR thumbnails (with the user's face from an upload), clone the style of top-ranking thumbnails; and produce SEO-optimised titles

LLM Mart · 0 points · 4 views 1 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download hyperfx-ai-marketing-skills-skills_youtube-abd711a.zip · 21 KB
Part of hyperfx-ai/marketing-skills — 30 skills

Install

skills CLI npx skills add https://github.com/hyperfx-ai/marketing-skills/tree/main/skills/youtube
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install hyperfx-ai-marketing-skills@llmmart
Git git clone https://github.com/hyperfx-ai/marketing-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole hyperfx-ai/marketing-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

YouTube

Fetch the full transcript of any YouTube video and turn it into whatever the user needs — summaries, blog posts, social content, quotes, show notes, or raw text. Then package videos for upload: high-CTR thumbnails, SEO titles, and descriptions.

Routing

User intent Where to go
Transcript, summary, repurposing, quotes, chapters This guide (below)
Thumbnails, style cloning, SEO titles/descriptions references/thumbnails.md

Requirements

  • Hyper MCP installed. https://app.hyperfx.ai/mcp
  • Sandbox text workflows: require ai_functions_run in the connected catalog and the sandbox toolkit. These scripts run inline LLM calls through the sandbox tool bridge.
  • YouTube toolkit enabled at https://app.hyperfx.ai/apps — provides youtube_video_transcripts_fetch and youtube_videos_read.
  • Thumbnail workflows additionally need the image generation and sandbox toolkits.

If search("youtube_video_transcripts_fetch") does not find youtube_video_transcripts_fetch, stop and tell the user to enable the YouTube toolkit in Hyper.

How to run the tools in this skill

Every tool in this skill is named by its canonical tool name. Run it with the call your surface gives you:

Surface Find a tool Run it
MCP client (Claude, Cursor, Codex, ChatGPT) search("<what you want to do>"), then describe("<name>") call("<name>", {...})
Hyper CLI hyperai search "<what you want to do>", then hyperai describe <name> hyperai call <name> --json '{...}'

If a tool is not found, its integration is not connected or not enabled for the workspace: stop and tell the user which integration to connect.

Two tools — pick the right one

Tool When to use Returns
youtube_video_transcripts_fetch You need the raw transcript text or timestamped segments. Fast, reliable, always get this first. Full text string + segments with start/duration timestamps
youtube_videos_read You need AI-powered extraction from the video — summaries, Q&A, topic segmentation, translation, visual descriptions. Free-form answer to your instruction

Default: start with youtube_video_transcripts_fetch. Use youtube_videos_read when you need something the raw text can't give you (e.g. visual descriptions, translation, or a structured extraction from a very long video).

Critical rules

  1. youtube_video_transcripts_fetch takes 15–30 seconds. It spins up an isolated sandbox. Tell the user it's running and to expect a short wait — don't make them think it's stuck.
  2. Both video IDs and full URLs are accepted. "NZLAdOL9fP8" and "https://www.youtube.com/watch?v=NZLAdOL9fP8" both work.
  3. Don't fabricate transcript content. Always fetch before summarizing. Never rely on training knowledge about what a specific video says.
  4. Very long videos (>2 hours): youtube_video_transcripts_fetch handles these fine. Only use youtube_videos_read on long videos if you specifically need AI-powered extraction — it can hit token limits on very long content.
  5. No transcript available: Some videos have transcripts disabled. If youtube_video_transcripts_fetch fails, try youtube_videos_read as a fallback — it uses a different extraction method.

Fetching the transcript

youtube_video_transcripts_fetch(
    video_id_or_url="https://www.youtube.com/watch?v=NZLAdOL9fP8",
    language="en"   # optional — omit to auto-detect
)

Response structure:

{
  "success": true,
  "video_id": "NZLAdOL9fP8",
  "language": "English (auto-generated)",
  "text": "Full transcript as one string...",
  "segments": [
    { "text": "This week we launched Hyper MCP.", "start": 0.0, "duration": 3.2 },
    { "text": "It brings Hyper's built-in tools...", "start": 3.2, "duration": 4.1 }
  ],
  "total_duration": 342.0
}

Use text for most tasks. Use segments when you need timestamps (e.g. chapters, clip references, karaoke captions).

Using youtube_videos_read for AI-powered extraction

youtube_videos_read(
    url="https://www.youtube.com/watch?v=NZLAdOL9fP8",
    instruction="Summarize the key points. Then list the main features demonstrated, with timestamps."
)

Good instruction examples:

  • "Extract every claim made about pricing or cost."
  • "List the action items mentioned, in order."
  • "Translate this to Spanish."
  • "What tools or products does the speaker mention by name?"
  • "Identify the main sections of this video and give me a timestamp for each."

What to do with the transcript

Once you have the text, ask the user what they need — or infer it from context:

What the user wants What to produce
Blog post Restructure the transcript into intro → sections → CTA. Clean up filler words. Add subheadings.
LinkedIn / Twitter post Extract the 1–2 sharpest insights. Rewrite in first person if it's the user's own video.
Summary 3–5 bullet points of key takeaways.
Show notes / description Title, 2-sentence summary, timestamped chapters, links mentioned.
Quote extraction Pull verbatim quotes with start timestamps from the segments array.
Repurpose for email Rewrite as a narrative email — opening hook, key insight, CTA.
Research / competitive analysis Summarize what the speaker claims, what products they recommend, and what pain points they describe.

Thumbnails and SEO packaging

For making or refreshing thumbnails, cloning the style of top-ranking thumbnails, adding the user's face, and generating SEO titles/descriptions, read references/thumbnails.md. Every thumbnail workflow is a sandbox script under scripts/ (generate_thumbnail.py, research_top_thumbnails.py, clone_top_thumbnail_style.py) — the reference doc is the routing table and the rules for using them.

Example outputs

Input: "Get the transcript of https://www.youtube.com/watch?v=NZLAdOL9fP8 and write a LinkedIn post from it"

Flow:

  1. Call youtube_video_transcripts_fetch(video_id_or_url="https://www.youtube.com/watch?v=NZLAdOL9fP8")
  2. Read the returned text
  3. Identify the 1–2 sharpest moments — what's surprising, useful, or quotable
  4. Draft a LinkedIn post in the speaker's voice (first person) with a hook and a clear point

Input: "Summarize this video for me: [URL]"

Flow:

  1. Call youtube_video_transcripts_fetch(video_id_or_url="[URL]")
  2. Return 4–6 bullet points of key takeaways, without padding or filler

Input: "Make me a thumbnail like the top videos for 'AI agents'"

Flow:

  1. Read references/thumbnails.md
  2. Run scripts/clone_top_thumbnail_style.py with query="AI agents" and the user's topic
  3. Show the top thumbnails, let the user pick a rank, re-run with chosen_rank to generate

Related skills

When to hand off Skill
Mining comments from YouTube videos for customer research customer-research
Finding top YouTube videos by topic Use youtube_videos_search_top directly
Generating video content video-generation

For title options, descriptions and thumbnail concepts, run generate_seo_titles.py, generate_seo_description.py and analyze_thumbnail_concepts.py under scripts/. They call ai_functions_run from the sandbox using fetched transcript and video context. Follow the output contracts in references/packaging-schemas.json; the thumbnail reference explains the workflow.

Files (marketing-skills)
  • references
    • composition-tips.md 3.1 KB
      # Thumbnail composition tips
      
      Quick reference for the agent when generating or critiquing a YouTube
      thumbnail concept. Use these as constraints inside the image-generation
      prompt, not as suggestions appended after the fact.
      
      ## The 1-3-5 rule
      
      A scrollable thumbnail can usually be parsed in under one second. Aim for:
      
      - **1 dominant focal point** (a face, an object, a number)
      - **≤3 visual elements** the eye needs to register
      - **≤5 words of overlay text**
      
      If the prompt has more than that, cut something before generating.
      
      ## Composition
      
      - **Rule of thirds.** Place the focal subject on a third-line, not centred,
        unless the concept explicitly calls for symmetry.
      - **Face on the left, text on the right** is the highest-CTR western default
        because viewers read left-to-right and the face commands attention first.
      - **Eye contact** with the camera outperforms profile shots for human
        subjects. Pupils visible, no sunglasses, no extreme angles.
      - **Negative space** behind text. Plain background or strong blur. Never put
        text over busy textures.
      - **Contrast.** Foreground subject must visibly separate from background.
        Use rim light, drop shadow, or a contrasting backdrop colour.
      
      ## Colour palette
      
      - 2-3 colours max, plus white/black for text.
      - High-saturation accent colour (yellow, red, neon green, magenta, cyan)
        for the focal element or text overlay.
      - Avoid YouTube-red as a dominant colour — it disappears against the UI.
      - Mood-to-palette cheat sheet:
        - Curiosity / mystery → deep blue + neon accent
        - Excitement / hype → red + yellow
        - Tutorial / clean → white background + single accent
        - Drama / personal → cinematic teal-and-orange
      
      ## Text overlay
      
      - Maximum 2-5 words. If you can't say it in 5, you don't need text.
      - Sans-serif, heavy weight (Inter Black, Anton, Bebek, Impact-style).
      - Stroke or drop shadow for legibility on every background.
      - Capitalise content words; one ALL-CAPS power word is fine, never two.
      - Place text where it does **not** overlap the YouTube duration badge
        (bottom-right corner) or the channel watermark (varies).
      
      ## Faces and emotion
      
      - Strong, identifiable emotion: shocked, mind-blown, excited, smug,
        knowing-smile. Neutral faces underperform.
      - One face per thumbnail unless the video is explicitly about contrast
        (debate, comparison, before/after).
      - If using the user's uploaded face, preserve identity (no aging, no
        gender swap, no race shift) and only adjust expression and lighting.
      
      ## Mobile-first
      
      - Most YouTube views happen on mobile. Open the generated image at
        ~280×158 px equivalent and check that:
        - The focal subject is still recognisable.
        - The text is still readable.
        - The visual hook still lands in <1 second.
      
      If any of those fail, tell the user and offer to regenerate with a
      simplified composition.
      
      ## What to avoid
      
      - Stock-photo poses (especially handshakes, generic smiles, headsets).
      - Generic AI-art tells (extra fingers, melted text, smooth plastic skin).
      - Watermarks of unknown brands you can't verify.
      - Misleading or fake "exclusive" badges.
      - Heavy emoji walls — one emoji max if the niche expects them.
      
    • packaging-schemas.json 4.9 KB
      {
        "analyze_thumbnail_concepts": {
          "type": "object",
          "properties": {
            "concepts": {
              "type": "array",
              "minItems": 5,
              "maxItems": 5,
              "items": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Short label for this concept (3-6 words)."
                  },
                  "composition": {
                    "type": "string",
                    "description": "What the viewer sees: subject placement, focal point, background, color palette, mood, lighting."
                  },
                  "hook": {
                    "type": "string",
                    "description": "Emotional or curiosity hook the thumbnail leans on (e.g. shock, transformation, before/after, big number, contrarian claim)."
                  },
                  "text_overlay": {
                    "type": "string",
                    "description": "2-5 word overlay text. Use uppercase if it should render as such. Empty string if no text overlay."
                  },
                  "ready_to_use_prompt": {
                    "type": "string",
                    "description": "Self-contained image-generation prompt that fully describes the thumbnail. Includes composition, subject(s), text overlay, color palette, lighting, and 16:9 framing."
                  }
                },
                "required": [
                  "title",
                  "composition",
                  "hook",
                  "text_overlay",
                  "ready_to_use_prompt"
                ],
                "additionalProperties": false
              }
            }
          },
          "required": [
            "concepts"
          ],
          "additionalProperties": false
        },
        "generate_seo_titles": {
          "type": "object",
          "properties": {
            "titles": {
              "type": "array",
              "minItems": 10,
              "maxItems": 10,
              "items": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "YouTube title, <=70 chars, includes the primary keyword naturally, no clickbait that misleads."
                  },
                  "style": {
                    "type": "string",
                    "enum": [
                      "curiosity-gap",
                      "listicle",
                      "contrarian",
                      "big-number",
                      "transformation",
                      "how-to",
                      "news-jack",
                      "question",
                      "before-after",
                      "bold-claim"
                    ]
                  },
                  "rationale": {
                    "type": "string",
                    "description": "One sentence explaining why this title works for SEO and CTR for the given topic."
                  },
                  "char_count": {
                    "type": "integer"
                  }
                },
                "required": [
                  "title",
                  "style",
                  "rationale",
                  "char_count"
                ],
                "additionalProperties": false
              }
            }
          },
          "required": [
            "titles"
          ],
          "additionalProperties": false
        },
        "generate_seo_description": {
          "type": "object",
          "properties": {
            "hook": {
              "type": "string",
              "description": "First line of the description, <=120 chars, hooks the viewer."
            },
            "summary": {
              "type": "string",
              "description": "2-3 sentence summary including the primary keyword naturally."
            },
            "key_takeaways": {
              "type": "array",
              "minItems": 3,
              "maxItems": 7,
              "items": {
                "type": "string"
              }
            },
            "timestamps": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "time": {
                    "type": "string",
                    "description": "MM:SS or HH:MM:SS"
                  },
                  "label": {
                    "type": "string"
                  }
                },
                "required": [
                  "time",
                  "label"
                ],
                "additionalProperties": false
              },
              "description": "Empty array if no transcript or chapters were provided."
            },
            "pull_quotes": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "maxItems": 3
            },
            "cta": {
              "type": "string",
              "description": "Single call-to-action line, <=140 chars."
            },
            "hashtags": {
              "type": "array",
              "minItems": 3,
              "maxItems": 8,
              "items": {
                "type": "string",
                "description": "Hashtag including the leading #."
              }
            },
            "flattened": {
              "type": "string",
              "description": "Final description ready to paste into YouTube, combining hook, summary, takeaways, timestamps, CTA, and hashtags with appropriate line breaks."
            }
          },
          "required": [
            "hook",
            "summary",
            "key_takeaways",
            "timestamps",
            "pull_quotes",
            "cta",
            "hashtags",
            "flattened"
          ],
          "additionalProperties": false
        }
      }
      
    • thumbnails.md 5.4 KB
      
      # Thumbnail Creator
      
      Create high click-through YouTube thumbnails and the SEO assets that go with them. Use sandbox scripts for YouTube research and image generation. Generate titles, descriptions and concepts through `ai_functions_run` in the bundled sandbox scripts.
      
      ## How this skill works
      
      Research, image and text workflows use the scripts under `scripts/`. Call the selected script through the sandbox toolkit and present its result. Generated images are persisted automatically and shown in chat. For text deliverables, follow the JSON schemas in `references/packaging-schemas.json`.
      
      Fetch transcript content with `youtube_video_transcripts_fetch`; use `youtube_videos_read` when visual context is required. Ground concepts, titles, summaries and timestamps in those results.
      
      ## Routing Table
      
      | User intent | Action | Reference |
      |-------------|-----------------|-----------|
      | "Make me a thumbnail for X" | `scripts/generate_thumbnail.py` | Direct generation, optional face/brand/style references |
      | "What thumbnails work for `<niche>`?" | `scripts/research_top_thumbnails.py` | Returns top videos + downloaded thumbnails |
      | "Make me a thumbnail like the top videos for `<niche>`" | `scripts/clone_top_thumbnail_style.py` | Research → user picks rank → style-cloned generation |
      | "Analyse this video's thumbnail concepts" (URL given) | `scripts/analyze_thumbnail_concepts.py` | Returns 5 ready-to-render thumbnail concepts |
      | "Give me good titles for this video" | `scripts/generate_seo_titles.py` | Returns 10 styled SEO title options |
      | "Write the description for this video" | `scripts/generate_seo_description.py` | Returns hook, summary, takeaways, timestamps, hashtags |
      
      ## Composed Workflows
      
      ### A. Thumbnail from idea (no face)
      
      1. Confirm the video topic / hook in one sentence with the user.
      2. Run `scripts/generate_thumbnail.py` with `prompt=<your refined prompt>`.
      3. Show the resulting `file_id` in chat (it renders automatically).
      
      ### B. Thumbnail with the user's face
      
      1. Ask the user to upload a clean photo of their face if they haven't already in the thread. Tell them a head-and-shoulders, well-lit photo works best.
      2. Once they upload, grab the file's `file_id` from the message attachments (it will look like `file_...`).
      3. Run `scripts/generate_thumbnail.py` with `prompt=<refined prompt>` and `face_file_ids=["<file_id>"]`.
      
      ### C. Style-cloned thumbnail
      
      1. Run `scripts/clone_top_thumbnail_style.py` with `query=<niche search term>`, `my_topic=<their video idea>`, optional `face_file_id`, and `top_k=5`.
      2. The script returns the top `top_k` thumbnails so the user can pick the rank they like.
      3. Re-run with `chosen_rank=<n>` to actually generate the cloned thumbnail.
      
      ### D. Full YouTube upload package
      
      For users prepping to publish. Run sequentially and present each result before moving to the next:
      
      1. `scripts/research_top_thumbnails.py` — what's working in the niche.
      2. `scripts/clone_top_thumbnail_style.py` — pick a winning style, generate the thumbnail.
      3. `scripts/generate_seo_titles.py` — 10 title options.
      4. `scripts/generate_seo_description.py` — full description with hashtags.
      
      ## Rules
      
      1. Treat the user's face like a sensitive asset: only use file_ids the user explicitly uploaded in this thread, and never invent or reuse them across users.
      2. When the user mentions a niche or competitor channel, run `research_top_thumbnails` first — never guess what's working.
      3. Don't dump base64 image data or thumbnail URLs into the chat. Pass the `file_id` and let the platform render it.
      4. For style-cloning, always show the user the top thumbnails first and let them pick the rank. Style-cloning a low-ranked or off-topic video produces bad results.
      5. If the user uploads multiple face photos, prefer the most recent and ignore the rest unless they ask you to compose them.
      6. Use `images_generate` for every generation: `model="nano-banana"` for cheap iteration, `model="nano-banana-pro"` only when the thumbnail needs strong rendered text inside the image, and `model="gpt-image-2.5-sunburst"` when composing multiple reference images (face + brand + product).
      
      ## Reference
      
      - `references/composition-tips.md` — universal composition rules (rule of thirds, contrast, eye-line).
      - `references/youtube-thumbnail-best-practices.md` — YouTube-specific patterns (16:9, mobile-safe text, CTR triggers).
      
      ## Structured text deliverables
      
      The text scripts call `ai_functions_run` through the sandbox tool bridge. They return objects matching `references/packaging-schemas.json`:
      
      - `analyze_thumbnail_concepts`: five concepts, each with title, composition, hook, text overlay and ready-to-use prompt. Send the chosen prompt to the image script only after the user selects a concept.
      - `generate_seo_titles`: ten titles, each with a supported style, rationale and character count. Keep titles within 70 characters and include the primary keyword naturally. Do not promise content the video does not contain.
      - `generate_seo_description`: hook, summary, key takeaways, timestamps, pull quotes, CTA, hashtags and a flattened description. Use only real timestamps and supported quotes; keep timestamps empty when the available source has none.
      
      Check required fields and item counts against the schemas before presenting the result. These scripts require `ai_functions_run` in the connected tool catalog. If unavailable, report the missing tool; do not claim the sandbox workflow succeeded. Tool and model failures must remain visible to the caller.
      
    • youtube-thumbnail-best-practices.md 4.3 KB
      # YouTube thumbnail best practices
      
      Channel-agnostic packaging guidance the agent should apply when designing
      a thumbnail or critiquing one. Pair with `composition-tips.md`, which
      covers the visual mechanics.
      
      ## CTR-driven mindset
      
      The thumbnail's only job is to win the click against ~12 sibling thumbnails
      on the suggested feed. Every design decision should be evaluated against:
      
      > "Does this make a viewer pause and click *instead* of the next video?"
      
      If the answer is "it looks nice", the thumbnail is failing.
      
      ## The packaging unit
      
      A thumbnail never works alone. It must form a coherent **packaging unit**
      with the title:
      
      - **Curiosity gap** between thumbnail visual and title text — the visual
        raises a question the title partially answers.
      - **No redundancy** — never use the same words on the thumbnail and in the
        title. Wasted real estate.
      - **Tone match** — sober thumbnail + clickbait title = trust collapse.
      
      When in doubt, write the title first, then design the thumbnail to create
      tension with it.
      
      ## Hook archetypes that consistently work
      
      Use these as a checklist when generating concepts:
      
      1. **Transformation / before-after** — clearly show two states.
      2. **Big number** — "$10K", "100×", "Day 47". Numbers must look hand-set,
         not auto-rendered.
      3. **Contrarian claim** — visually contradict the audience's prior.
      4. **Curiosity gap** — show the *outcome* but obscure the *cause*.
      5. **Authority** — recognisable expert / celebrity / brand on screen.
      6. **Reaction shot** — strong emotion at something the viewer can't see.
      7. **Comparison / vs.** — two subjects with a divider.
      8. **Process reveal** — peek inside something normally hidden.
      9. **Risk / stakes** — implied danger, deadline, or scarcity.
      10. **Pattern interrupt** — visually breaks the niche's convention.
      
      Always pick one primary hook per thumbnail. Stacking hooks dilutes them.
      
      ## Niche conventions
      
      Quickly check the top results for the user's search term (use
      `research_top_thumbnails`) and note the niche convention before designing.
      Common conventions:
      
      - **Tech / coding** — code editor screenshots, dark backgrounds, neon
        highlights, expressive faces pointing at things.
      - **Finance** — green/red, large numbers, charts going up-and-to-the-right.
      - **Lifestyle / vlog** — bright outdoor shots, full body in frame, warm
        colour grade.
      - **Educational / explainer** — diagrams, arrows, isolated subject on a
        flat background.
      
      You can either follow the convention (safer, blends into the feed) or
      intentionally break it (riskier, but a strong differentiator). Tell the
      user which strategy you're using and why.
      
      ## Title best practices
      
      - **Length** — keep under 70 characters so it never truncates on mobile or
        the suggested feed.
      - **Keyword early** — primary keyword in the first 50 characters helps
        search ranking.
      - **Avoid clickbait that misleads.** YouTube's ranking model penalises
        high-CTR-but-low-watch-time videos harder than low-CTR ones.
      - **Test variants.** When the user asks for titles, give them 10 stylistic
        variants (handled by `generate_seo_titles.py`) — they should A/B test.
      
      ## Description best practices
      
      The first 1-2 lines show in search snippets and above the fold; everything
      else is for the algorithm.
      
      - **Hook line** — first line restates the value prop, includes the primary
        keyword.
      - **Summary** — 2-3 sentences, keyword-natural, no stuffing.
      - **Key takeaways** — bulleted, scannable.
      - **Timestamps** — if you have them, include them. They both help retention
        and unlock chapters.
      - **CTA** — single, specific call-to-action. "Subscribe for more" is dead;
        "Get the template at <link>" works.
      - **Hashtags** — 3-5 relevant hashtags at the very end. The first 3 show
        above the title.
      
      `generate_seo_description.py` produces this whole structure plus a
      `flattened` field the user can paste straight into YouTube.
      
      ## Iteration loop
      
      When the user is iterating on a thumbnail:
      
      1. Generate 1 thumbnail at a time. Don't dump 4 variants without comment.
      2. After each generation, name the **specific** thing you'd change next
         ("the text is competing with the face — move it lower", not "we could
         try variations").
      3. Stop iterating after 3 attempts on the same concept. If it isn't working
         after 3 tries, the concept is wrong; go back to
         `analyze_thumbnail_concepts.py` for a different angle.
      
  • scripts
    • analyze_thumbnail_concepts.py 5.2 KB
      """Extract 5 ready-to-render thumbnail concepts from a YouTube video.
      
      Pipeline:
      1. ``youtube_videos_read`` summarises the video (title, hook, key visuals, mood).
      2. ``ai_functions_run`` coerces that summary into a strict 5-concept JSON schema
         where each concept includes a one-line title, the visual composition, the
         emotional hook, and a ``ready_to_use_prompt`` that can be passed straight
         into ``generate_thumbnail.py``.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any
      
      from seti.sandbox import call_tool
      
      THUMBNAIL_CONCEPTS_SCHEMA: dict[str, Any] = {
          "type": "object",
          "properties": {
              "concepts": {
                  "type": "array",
                  "minItems": 5,
                  "maxItems": 5,
                  "items": {
                      "type": "object",
                      "properties": {
                          "title": {
                              "type": "string",
                              "description": "Short label for this concept (3-6 words).",
                          },
                          "composition": {
                              "type": "string",
                              "description": (
                                  "What the viewer sees: subject placement, focal "
                                  "point, background, color palette, mood, lighting."
                              ),
                          },
                          "hook": {
                              "type": "string",
                              "description": (
                                  "Emotional or curiosity hook the thumbnail leans on "
                                  "(e.g. shock, transformation, before/after, big "
                                  "number, contrarian claim)."
                              ),
                          },
                          "text_overlay": {
                              "type": "string",
                              "description": (
                                  "2-5 word overlay text. Use uppercase if it should "
                                  "render as such. Empty string if no text overlay."
                              ),
                          },
                          "ready_to_use_prompt": {
                              "type": "string",
                              "description": (
                                  "Self-contained image-generation prompt that fully "
                                  "describes the thumbnail. Includes composition, "
                                  "subject(s), text overlay, color palette, lighting, "
                                  "and 16:9 framing."
                              ),
                          },
                      },
                      "required": [
                          "title",
                          "composition",
                          "hook",
                          "text_overlay",
                          "ready_to_use_prompt",
                      ],
                      "additionalProperties": False,
                  },
              }
          },
          "required": ["concepts"],
          "additionalProperties": False,
      }
      
      
      CONCEPT_EXTRACTION_PROMPT = (
          "You are a YouTube thumbnail strategist. Given a video summary, design 5 "
          "distinct high-CTR thumbnail concepts. Each concept should use a different "
          "hook (e.g. shock, transformation, contrarian, big-number, curiosity gap). "
          "Compositions must be optimised for 16:9 and remain readable on a small "
          "mobile thumbnail. ready_to_use_prompt must be a complete image-generation "
          "prompt with subject, composition, color palette, lighting, mood, and "
          "explicit '16:9 YouTube thumbnail' framing. Do not reference brands you "
          "are not sure about."
      )
      
      
      async def run(youtube_url: str) -> dict[str, Any]:
          summary = await call_tool(
              "youtube_videos_read",
              url=youtube_url,
              instruction=(
                  "Describe this video for a thumbnail designer. Cover the topic, "
                  "the host's appearance and emotion, the strongest visual moments, "
                  "any on-screen text or charts, the overall mood, and the single "
                  "biggest hook a viewer would click for. Be concise."
              ),
          )
      
          summary_text = summary if isinstance(summary, str) else json.dumps(summary)
      
          concepts_result = await call_tool(
              "ai_functions_run",
              instructions=CONCEPT_EXTRACTION_PROMPT,
              input={"video_summary": summary_text, "youtube_url": youtube_url},
              output_format="json",
              output_json_schema=THUMBNAIL_CONCEPTS_SCHEMA,
              performance="fast",
          )
      
          if not concepts_result.get("success"):
              raise RuntimeError(
                  concepts_result.get("error")
                  or concepts_result.get("message")
                  or "AI generation failed"
              )
          item = concepts_result["results"][0]
          if not item.get("success") or not isinstance(item.get("output_json"), dict):
              raise RuntimeError(
                  item.get("error") or "AI generation returned no structured output"
              )
          output_json = item["output_json"]
          concepts = output_json.get("concepts", [])
      
          return {
              "youtube_url": youtube_url,
              "video_summary": summary_text,
              "concepts": concepts,
          }
      
      
      EXAMPLE_INPUT = {"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • clone_top_thumbnail_style.py 5.4 KB
      """Clone the style of one of the top-performing thumbnails for a search term.
      
      Two-phase script:
      
      * Phase 1 (no ``chosen_rank``): runs ``research_top_thumbnails`` and returns
        the top ``top_k`` candidates so the agent can present them to the user and
        ask which rank to clone.
      
      * Phase 2 (``chosen_rank`` set): looks up the chosen thumbnail's
        ``thumbnail_file_id`` and runs ``images_generate`` with that file as the
        style reference. If a ``face_file_id`` is provided, the prompt is
        augmented to composite the user's face into the cloned style.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any
      
      from seti.sandbox import call_tool
      
      
      async def _research(query: str, top_k: int) -> list[dict[str, Any]]:
          res = await call_tool(
              "youtube_videos_search_top",
              query=query,
              max_results=top_k,
              sort_by="views",
              download_thumbnails=True,
          )
          items = res.get("items", []) if isinstance(res, dict) else []
          return items
      
      
      def _build_clone_prompt(my_topic: str, has_face: bool, source_title: str | None) -> str:
          base = (
              f"Recreate this exact thumbnail style and composition, but for a video "
              f"about: {my_topic.strip()}. Match the color palette, lighting, "
              f"text-overlay style, character pose, and emotional tone of the "
              f"reference image. Keep the 16:9 framing and a single dominant focal "
              f"point. Replace any subject-specific imagery so it clearly relates "
              f"to '{my_topic.strip()}'."
          )
          if source_title:
              base += f" The reference is from a video titled '{source_title}'."
          if has_face:
              base += (
                  " Replace the main person in the thumbnail with the person from "
                  "the supplied face reference, preserving their identity, "
                  "expression style, and approximate pose. Composite cleanly with "
                  "matched lighting."
              )
          return base
      
      
      async def run(
          query: str,
          my_topic: str,
          face_file_id: str | None = None,
          top_k: int = 5,
          chosen_rank: int | None = None,
      ) -> dict[str, Any]:
          if not query or not query.strip():
              raise ValueError("query is required")
          if not my_topic or not my_topic.strip():
              raise ValueError("my_topic is required")
      
          items = await _research(query, top_k)
      
          if chosen_rank is None:
              return {
                  "phase": "research",
                  "query": query,
                  "my_topic": my_topic,
                  "candidates": [
                      {
                          "rank": item.get("rank"),
                          "title": item.get("title"),
                          "channel": item.get("channel"),
                          "url": item.get("url"),
                          "view_count": item.get("view_count"),
                          "thumbnail_url": item.get("thumbnail_url"),
                          "thumbnail_file_id": item.get("thumbnail_file_id"),
                      }
                      for item in items
                  ],
                  "next_step": (
                      "Show these candidates to the user. Then re-run this script "
                      "with chosen_rank=<n> to clone that thumbnail's style for "
                      f"'{my_topic}'."
                  ),
              }
      
          chosen = next((it for it in items if it.get("rank") == chosen_rank), None)
          if chosen is None:
              raise ValueError(
                  f"chosen_rank={chosen_rank} not found in top {top_k} results "
                  f"for query '{query}'"
              )
      
          style_file_id = chosen.get("thumbnail_file_id")
          if not style_file_id:
              raise RuntimeError(
                  f"No thumbnail_file_id available for rank {chosen_rank}. "
                  "The thumbnail download likely failed; try a different rank."
              )
      
          prompt = _build_clone_prompt(
              my_topic=my_topic,
              has_face=bool(face_file_id),
              source_title=chosen.get("title"),
          )
      
          if face_file_id:
              # Sunburst composes several references while preserving a person's
              # identity; the Gemini models are better with one style reference.
              chosen_model = "gpt-image-2.5-sunburst"
              reference_images = [style_file_id, face_file_id]
          else:
              chosen_model = "nano-banana-pro"
              reference_images = [style_file_id]
          used_tool = "images_generate"
          result = await call_tool(
              used_tool,
              requests=[
                  {"id": "thumbnail", "prompt": prompt, "reference_images": reference_images}
              ],
              model=chosen_model,
              aspect_ratio="16:9",
              quality="standard",
          )
      
          images = result.get("images", []) if isinstance(result, dict) else []
          if not images:
              raise RuntimeError(f"{used_tool} returned no images: {result}")
          primary = images[0]
      
          return {
              "phase": "generated",
              "query": query,
              "my_topic": my_topic,
              "source": {
                  "rank": chosen.get("rank"),
                  "title": chosen.get("title"),
                  "channel": chosen.get("channel"),
                  "url": chosen.get("url"),
                  "thumbnail_file_id": style_file_id,
              },
              "tool_used": used_tool,
              "prompt": prompt,
              "primary": {
                  "file_id": primary.get("file_id"),
                  "url": primary.get("url"),
              },
          }
      
      
      EXAMPLE_INPUT = {
          "query": "ai agent tutorial",
          "my_topic": "Building a customer support agent with Claude",
          "top_k": 5,
      }
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • generate_seo_description.py 5.3 KB
      """Generate a structured, SEO-optimised YouTube description.
      
      Produces a hook line, a 2-3 sentence summary, key takeaways, suggested
      timestamps (when transcript or chapters are provided), pull-quotes, a CTA,
      and a list of relevant hashtags. The output is structured so the agent can
      either render it for the user or paste a flattened version into YouTube.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any
      
      from seti.sandbox import call_tool
      
      SEO_DESCRIPTION_SCHEMA: dict[str, Any] = {
          "type": "object",
          "properties": {
              "hook": {
                  "type": "string",
                  "description": "First line of the description, <=120 chars, hooks the viewer.",
              },
              "summary": {
                  "type": "string",
                  "description": "2-3 sentence summary including the primary keyword naturally.",
              },
              "key_takeaways": {
                  "type": "array",
                  "minItems": 3,
                  "maxItems": 7,
                  "items": {"type": "string"},
              },
              "timestamps": {
                  "type": "array",
                  "items": {
                      "type": "object",
                      "properties": {
                          "time": {
                              "type": "string",
                              "description": "MM:SS or HH:MM:SS",
                          },
                          "label": {"type": "string"},
                      },
                      "required": ["time", "label"],
                      "additionalProperties": False,
                  },
                  "description": ("Empty array if no transcript or chapters were provided."),
              },
              "pull_quotes": {
                  "type": "array",
                  "items": {"type": "string"},
                  "maxItems": 3,
              },
              "cta": {
                  "type": "string",
                  "description": "Single call-to-action line, <=140 chars.",
              },
              "hashtags": {
                  "type": "array",
                  "minItems": 3,
                  "maxItems": 8,
                  "items": {
                      "type": "string",
                      "description": "Hashtag including the leading #.",
                  },
              },
              "flattened": {
                  "type": "string",
                  "description": (
                      "Final description ready to paste into YouTube, combining "
                      "hook, summary, takeaways, timestamps, CTA, and hashtags "
                      "with appropriate line breaks."
                  ),
              },
          },
          "required": [
              "hook",
              "summary",
              "key_takeaways",
              "timestamps",
              "pull_quotes",
              "cta",
              "hashtags",
              "flattened",
          ],
          "additionalProperties": False,
      }
      
      
      SEO_DESCRIPTION_PROMPT = (
          "You are a YouTube SEO copywriter. Write a structured, high-CTR video "
          "description for the given video. Include the primary keyword naturally "
          "in the hook and summary. Generate timestamps only if a transcript or "
          "chapter list is provided. Hashtags should be lowercase, no spaces, "
          "directly relevant. The 'flattened' field must be the final string the "
          "user can paste into YouTube — assemble hook, summary, takeaways "
          "(bulleted with '•'), timestamps (one per line as 'MM:SS — Label'), "
          "the CTA, and hashtags on one line at the end. Avoid spammy emoji "
          "walls and never invent facts not supported by the inputs."
      )
      
      
      async def run(
          topic: str,
          primary_keyword: str | None = None,
          audience: str | None = None,
          transcript_summary: str | None = None,
          chapters: list[dict[str, str]] | None = None,
          cta_link: str | None = None,
      ) -> dict[str, Any]:
          if not topic or not topic.strip():
              raise ValueError("topic is required")
      
          payload: dict[str, Any] = {"topic": topic}
          if primary_keyword:
              payload["primary_keyword"] = primary_keyword
          if audience:
              payload["audience"] = audience
          if transcript_summary:
              payload["transcript_summary"] = transcript_summary
          if chapters:
              payload["chapters"] = chapters
          if cta_link:
              payload["cta_link"] = cta_link
      
          res = await call_tool(
              "ai_functions_run",
              instructions=SEO_DESCRIPTION_PROMPT,
              input=payload,
              output_format="json",
              output_json_schema=SEO_DESCRIPTION_SCHEMA,
              performance="fast",
          )
      
          if not res.get("success"):
              raise RuntimeError(
                  res.get("error") or res.get("message") or "AI generation failed"
              )
          item = res["results"][0]
          if not item.get("success") or not isinstance(item.get("output_json"), dict):
              raise RuntimeError(
                  item.get("error") or "AI generation returned no structured output"
              )
          output_json = item["output_json"]
          return {
              "topic": topic,
              "primary_keyword": primary_keyword,
              "description": output_json,
          }
      
      
      EXAMPLE_INPUT = {
          "topic": "Building an AI customer support agent in a weekend",
          "primary_keyword": "AI customer support agent",
          "audience": "indie SaaS founders",
          "transcript_summary": (
              "We build a Claude-powered support agent over a weekend, hook it up "
              "to a knowledge base, and ship it to a real Intercom inbox."
          ),
      }
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • generate_seo_titles.py 4.3 KB
      """Generate 10 SEO-optimised YouTube title variants.
      
      Each title is tagged with the persuasion style it leans on (curiosity-gap,
      listicle, contrarian, big-number, transformation, how-to, news-jack,
      question, before/after, bold-claim) so the user can quickly pick a tone that
      fits their channel.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any
      
      from seti.sandbox import call_tool
      
      SEO_TITLES_SCHEMA: dict[str, Any] = {
          "type": "object",
          "properties": {
              "titles": {
                  "type": "array",
                  "minItems": 10,
                  "maxItems": 10,
                  "items": {
                      "type": "object",
                      "properties": {
                          "title": {
                              "type": "string",
                              "description": (
                                  "YouTube title, <=70 chars, includes the primary "
                                  "keyword naturally, no clickbait that misleads."
                              ),
                          },
                          "style": {
                              "type": "string",
                              "enum": [
                                  "curiosity-gap",
                                  "listicle",
                                  "contrarian",
                                  "big-number",
                                  "transformation",
                                  "how-to",
                                  "news-jack",
                                  "question",
                                  "before-after",
                                  "bold-claim",
                              ],
                          },
                          "rationale": {
                              "type": "string",
                              "description": (
                                  "One sentence explaining why this title works for "
                                  "SEO and CTR for the given topic."
                              ),
                          },
                          "char_count": {"type": "integer"},
                      },
                      "required": ["title", "style", "rationale", "char_count"],
                      "additionalProperties": False,
                  },
              }
          },
          "required": ["titles"],
          "additionalProperties": False,
      }
      
      
      SEO_TITLE_PROMPT = (
          "You are a YouTube SEO and packaging expert. Given a topic, target "
          "keyword, and audience, write 10 distinct title variants. Each must use "
          "a different persuasion style from the enum. Keep all titles under 70 "
          "characters. Include the primary keyword naturally in at least 7 of "
          "them. Avoid all-caps spam, misleading clickbait, and emojis unless the "
          "audience explicitly expects them. Set char_count to the exact length "
          "of the title string."
      )
      
      
      async def run(
          topic: str,
          primary_keyword: str | None = None,
          audience: str | None = None,
          transcript_summary: str | None = None,
      ) -> dict[str, Any]:
          if not topic or not topic.strip():
              raise ValueError("topic is required")
      
          payload: dict[str, Any] = {"topic": topic}
          if primary_keyword:
              payload["primary_keyword"] = primary_keyword
          if audience:
              payload["audience"] = audience
          if transcript_summary:
              payload["transcript_summary"] = transcript_summary
      
          res = await call_tool(
              "ai_functions_run",
              instructions=SEO_TITLE_PROMPT,
              input=payload,
              output_format="json",
              output_json_schema=SEO_TITLES_SCHEMA,
              performance="fast",
          )
      
          if not res.get("success"):
              raise RuntimeError(
                  res.get("error") or res.get("message") or "AI generation failed"
              )
          item = res["results"][0]
          if not item.get("success") or not isinstance(item.get("output_json"), dict):
              raise RuntimeError(
                  item.get("error") or "AI generation returned no structured output"
              )
          output_json = item["output_json"]
          return {
              "topic": topic,
              "primary_keyword": primary_keyword,
              "titles": output_json.get("titles", []),
          }
      
      
      EXAMPLE_INPUT = {
          "topic": "Building an AI customer support agent in a weekend",
          "primary_keyword": "AI customer support agent",
          "audience": "indie SaaS founders",
      }
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • generate_thumbnail.py 4.4 KB
      """Generate a single YouTube thumbnail.
      
      Every call goes through ``images_generate``; the inputs pick the ``model``:
      
      - ``style_reference_file_id`` set     -> ``nano-banana`` (``nano-banana-pro``
                                                when the prompt needs rendered text)
                                                with that file in ``reference_images``.
      - ``brand_file_ids`` or
        ``face_file_ids`` set               -> ``gpt-image-2.5-sunburst`` with the
                                                combined ``reference_images`` (best
                                                at composing several references).
      - otherwise                           -> ``nano-banana-pro`` if the prompt
                                                has explicit text overlay,
                                                ``nano-banana`` otherwise.
      
      All tool calls go through the sandbox RPC, so generated images are persisted
      as ``DBFile``s in the conversation thread and metering fires automatically.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any, Literal
      
      from seti.sandbox import call_tool
      
      # Tokens that suggest the prompt requires legible text rendering inside the
      # image. Nano-banana 'pro' is materially better at text than 'flash'.
      _TEXT_HEAVY_TOKENS = (
          '"',
          "“",
          "”",
          "headline",
          "headline:",
          "text overlay",
          "text:",
          "caption",
          "subtitle",
          "title text",
          "label",
          "logo text",
      )
      
      
      def _looks_text_heavy(prompt: str) -> bool:
          lowered = prompt.lower()
          return any(tok in lowered for tok in _TEXT_HEAVY_TOKENS)
      
      
      def _normalise_aspect(aspect_ratio: str | None) -> str:
          return aspect_ratio or "16:9"
      
      
      # images_generate takes a quality tier; map the script's resolution knob onto it.
      _QUALITY_BY_SIZE = {"1K": "draft", "2K": "standard", "4K": "high"}
      
      
      async def run(
          prompt: str,
          face_file_ids: list[str] | None = None,
          brand_file_ids: list[str] | None = None,
          style_reference_file_id: str | None = None,
          aspect_ratio: str = "16:9",
          image_size: Literal["1K", "2K", "4K"] = "2K",
          model: Literal["pro", "flash", "auto"] = "auto",
          n: int = 1,
      ) -> dict[str, Any]:
          if not prompt or not prompt.strip():
              raise ValueError("prompt is required")
      
          refs: list[str] = []
          if face_file_ids:
              refs.extend(face_file_ids)
          if brand_file_ids:
              refs.extend(brand_file_ids)
      
          aspect = _normalise_aspect(aspect_ratio)
          quality = _QUALITY_BY_SIZE[image_size]
      
          def gemini_model() -> str:
              if model == "pro":
                  return "nano-banana-pro"
              if model == "flash":
                  return "nano-banana"
              return "nano-banana-pro" if _looks_text_heavy(prompt) else "nano-banana"
      
          request: dict[str, Any] = {"id": "thumbnail", "prompt": prompt}
          if style_reference_file_id:
              chosen_model = gemini_model()
              request["reference_images"] = [style_reference_file_id]
          elif refs:
              chosen_model = "gpt-image-2.5-sunburst"
              request["reference_images"] = refs
          else:
              chosen_model = gemini_model()
      
          result = await call_tool(
              "images_generate",
              requests=[request],
              n=n,
              model=chosen_model,
              aspect_ratio=aspect,
              quality=quality,
          )
      
          images = result.get("images", []) if isinstance(result, dict) else []
          if not images:
              raise RuntimeError(f"images_generate returned no images: {result}")
      
          primary = images[0]
          return {
              "tool_used": "images_generate",
              "model": chosen_model,
              "aspect_ratio": aspect,
              "image_size": image_size,
              "quality": quality,
              "primary": {
                  "file_id": primary.get("file_id"),
                  "url": primary.get("url"),
              },
              "all_images": [
                  {"file_id": img.get("file_id"), "url": img.get("url")} for img in images
              ],
              "reference_count": len(refs) + (1 if style_reference_file_id else 0),
          }
      
      
      EXAMPLE_INPUT = {
          "prompt": (
              "16:9 YouTube thumbnail, smiling young engineer pointing at a glowing "
              "AI brain hologram on the right, dark studio background with neon "
              "blue rim light, bold yellow text overlay 'AI AGENTS EXPLAINED' on "
              "the left, cinematic depth of field"
          ),
      }
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • research_top_thumbnails.py 2.1 KB
      """Research the top-performing YouTube videos for a search term.
      
      Calls the new ``youtube_videos_search_top`` tool with ``download_thumbnails=True`` so
      that every result has a ``thumbnail_file_id`` that subsequent scripts (e.g.
      ``clone_top_thumbnail_style``) can pass into image-edit tools as a reference.
      
      Returns a compact, ranked JSON shape that the agent can render or hand back
      to the user.
      """
      
      from __future__ import annotations
      
      import asyncio
      import json
      from typing import Any, Literal
      
      from seti.sandbox import call_tool
      
      
      async def run(
          query: str,
          max_results: int = 10,
          sort_by: Literal["relevance", "views", "date"] = "views",
          region: str | None = None,
          upload_date: Literal["hour", "today", "week", "month", "year"] | None = None,
      ) -> dict[str, Any]:
          res = await call_tool(
              "youtube_videos_search_top",
              query=query,
              max_results=max_results,
              sort_by=sort_by,
              region=region,
              upload_date=upload_date,
              download_thumbnails=True,
          )
      
          items = res.get("items", []) if isinstance(res, dict) else []
          ranked: list[dict[str, Any]] = []
          for item in items:
              ranked.append(
                  {
                      "rank": item.get("rank"),
                      "title": item.get("title"),
                      "channel": item.get("channel"),
                      "url": item.get("url"),
                      "video_id": item.get("video_id"),
                      "view_count": item.get("view_count"),
                      "duration": item.get("duration"),
                      "published_at": item.get("published_at"),
                      "thumbnail_url": item.get("thumbnail_url"),
                      "thumbnail_file_id": item.get("thumbnail_file_id"),
                  }
              )
      
          return {
              "query": query,
              "sort_by": sort_by,
              "region": region,
              "result_count": len(ranked),
              "results": ranked,
          }
      
      
      EXAMPLE_INPUT = {"query": "ai agent tutorial", "max_results": 5}
      
      
      async def main() -> None:
          result = await run(**EXAMPLE_INPUT)
          print(json.dumps(result, indent=2, ensure_ascii=True))
      
      
      if __name__ == "__main__":
          asyncio.run(main())
      
  • SKILL.md 8.9 KB
    ---
    name: youtube
    description: Work with YouTube content end to end — fetch transcripts and turn them into summaries, blog posts, social content, quotes, or show notes; create high-CTR thumbnails (with the user's face from an upload), clone the style of top-ranking thumbnails; and produce SEO-optimised titles + descriptions. Use when the user pastes a YouTube URL, wants to repurpose video content, research competitor videos, make or refresh a thumbnail, or package a video for upload.
    use_cases:
      - Get the transcript of a YouTube video
      - Summarize a YouTube video or extract key points without watching
      - Turn a video into a blog post, LinkedIn post, or show notes
      - Pull verbatim quotes with timestamps
      - Generate a YouTube thumbnail from a video idea, transcript, or YouTube URL
      - Research the top-performing thumbnails for a search term and clone their style
      - Add the user's face (uploaded as an image) to a thumbnail
      - Produce an SEO-optimised title set and description for an upcoming video
    triggers:
      - youtube
      - youtube transcript
      - youtube video
      - video summary
      - repurpose video
      - show notes
      - thumbnail
      - youtube thumbnail
      - thumbnail clone
      - SEO titles
      - video description
      - youtube title
    requires_toolkits:
      - youtube_toolkit
    suggested_toolkits:
      - image_gen
      - sandbox
      - file_manager
    icon: youtube
    short_description: Fetch transcripts, repurpose video content, and create thumbnails for YouTube.
    ---
    
    # YouTube
    
    Fetch the full transcript of any YouTube video and turn it into whatever the user needs — summaries, blog posts, social content, quotes, show notes, or raw text. Then package videos for upload: high-CTR thumbnails, SEO titles, and descriptions.
    
    ## Routing
    
    | User intent | Where to go |
    | --- | --- |
    | Transcript, summary, repurposing, quotes, chapters | This guide (below) |
    | Thumbnails, style cloning, SEO titles/descriptions | `references/thumbnails.md` |
    
    ## Requirements
    
    - **Hyper MCP installed.** [https://app.hyperfx.ai/mcp](https://app.hyperfx.ai/mcp)
    - **Sandbox text workflows:** require `ai_functions_run` in the connected catalog and the sandbox toolkit. These scripts run inline LLM calls through the sandbox tool bridge.
    - **YouTube toolkit enabled** at [https://app.hyperfx.ai/apps](https://app.hyperfx.ai/apps) — provides `youtube_video_transcripts_fetch` and `youtube_videos_read`.
    - Thumbnail workflows additionally need the image generation and sandbox toolkits.
    
    If `search("youtube_video_transcripts_fetch")` does not find `youtube_video_transcripts_fetch`, stop and tell the user to enable the YouTube toolkit in Hyper.
    
    ### How to run the tools in this skill
    
    Every tool in this skill is named by its canonical tool name. Run it with the call your surface gives you:
    
    | Surface | Find a tool | Run it |
    | --- | --- | --- |
    | MCP client (Claude, Cursor, Codex, ChatGPT) | `search("<what you want to do>")`, then `describe("<name>")` | `call("<name>", {...})` |
    | Hyper CLI | `hyperai search "<what you want to do>"`, then `hyperai describe <name>` | `hyperai call <name> --json '{...}'` |
    
    If a tool is not found, its integration is not connected or not enabled for the workspace: stop and tell the user which integration to connect.
    
    ## Two tools — pick the right one
    
    | Tool | When to use | Returns |
    | --- | --- | --- |
    | `youtube_video_transcripts_fetch` | You need the raw transcript text or timestamped segments. Fast, reliable, always get this first. | Full text string + segments with start/duration timestamps |
    | `youtube_videos_read` | You need AI-powered extraction from the video — summaries, Q&A, topic segmentation, translation, visual descriptions. | Free-form answer to your instruction |
    
    **Default: start with `youtube_video_transcripts_fetch`.** Use `youtube_videos_read` when you need something the raw text can't give you (e.g. visual descriptions, translation, or a structured extraction from a very long video).
    
    ## Critical rules
    
    1. **`youtube_video_transcripts_fetch` takes 15–30 seconds.** It spins up an isolated sandbox. Tell the user it's running and to expect a short wait — don't make them think it's stuck.
    2. **Both video IDs and full URLs are accepted.** `"NZLAdOL9fP8"` and `"https://www.youtube.com/watch?v=NZLAdOL9fP8"` both work.
    3. **Don't fabricate transcript content.** Always fetch before summarizing. Never rely on training knowledge about what a specific video says.
    4. **Very long videos (>2 hours):** `youtube_video_transcripts_fetch` handles these fine. Only use `youtube_videos_read` on long videos if you specifically need AI-powered extraction — it can hit token limits on very long content.
    5. **No transcript available:** Some videos have transcripts disabled. If `youtube_video_transcripts_fetch` fails, try `youtube_videos_read` as a fallback — it uses a different extraction method.
    
    ## Fetching the transcript
    
    ```python
    youtube_video_transcripts_fetch(
        video_id_or_url="https://www.youtube.com/watch?v=NZLAdOL9fP8",
        language="en"   # optional — omit to auto-detect
    )
    ```
    
    **Response structure:**
    
    ```json
    {
      "success": true,
      "video_id": "NZLAdOL9fP8",
      "language": "English (auto-generated)",
      "text": "Full transcript as one string...",
      "segments": [
        { "text": "This week we launched Hyper MCP.", "start": 0.0, "duration": 3.2 },
        { "text": "It brings Hyper's built-in tools...", "start": 3.2, "duration": 4.1 }
      ],
      "total_duration": 342.0
    }
    ```
    
    Use `text` for most tasks. Use `segments` when you need timestamps (e.g. chapters, clip references, karaoke captions).
    
    ## Using youtube_videos_read for AI-powered extraction
    
    ```python
    youtube_videos_read(
        url="https://www.youtube.com/watch?v=NZLAdOL9fP8",
        instruction="Summarize the key points. Then list the main features demonstrated, with timestamps."
    )
    ```
    
    Good `instruction` examples:
    - `"Extract every claim made about pricing or cost."`
    - `"List the action items mentioned, in order."`
    - `"Translate this to Spanish."`
    - `"What tools or products does the speaker mention by name?"`
    - `"Identify the main sections of this video and give me a timestamp for each."`
    
    ## What to do with the transcript
    
    Once you have the text, ask the user what they need — or infer it from context:
    
    | What the user wants | What to produce |
    | --- | --- |
    | Blog post | Restructure the transcript into intro → sections → CTA. Clean up filler words. Add subheadings. |
    | LinkedIn / Twitter post | Extract the 1–2 sharpest insights. Rewrite in first person if it's the user's own video. |
    | Summary | 3–5 bullet points of key takeaways. |
    | Show notes / description | Title, 2-sentence summary, timestamped chapters, links mentioned. |
    | Quote extraction | Pull verbatim quotes with `start` timestamps from the segments array. |
    | Repurpose for email | Rewrite as a narrative email — opening hook, key insight, CTA. |
    | Research / competitive analysis | Summarize what the speaker claims, what products they recommend, and what pain points they describe. |
    
    ## Thumbnails and SEO packaging
    
    For making or refreshing thumbnails, cloning the style of top-ranking thumbnails, adding the user's face, and generating SEO titles/descriptions, read `references/thumbnails.md`. Every thumbnail workflow is a sandbox script under `scripts/` (`generate_thumbnail.py`, `research_top_thumbnails.py`, `clone_top_thumbnail_style.py`) — the reference doc is the routing table and the rules for using them.
    
    ## Example outputs
    
    **Input:** `"Get the transcript of https://www.youtube.com/watch?v=NZLAdOL9fP8 and write a LinkedIn post from it"`
    
    **Flow:**
    1. Call `youtube_video_transcripts_fetch(video_id_or_url="https://www.youtube.com/watch?v=NZLAdOL9fP8")`
    2. Read the returned `text`
    3. Identify the 1–2 sharpest moments — what's surprising, useful, or quotable
    4. Draft a LinkedIn post in the speaker's voice (first person) with a hook and a clear point
    
    **Input:** `"Summarize this video for me: [URL]"`
    
    **Flow:**
    1. Call `youtube_video_transcripts_fetch(video_id_or_url="[URL]")`
    2. Return 4–6 bullet points of key takeaways, without padding or filler
    
    **Input:** `"Make me a thumbnail like the top videos for 'AI agents'"`
    
    **Flow:**
    1. Read `references/thumbnails.md`
    2. Run `scripts/clone_top_thumbnail_style.py` with `query="AI agents"` and the user's topic
    3. Show the top thumbnails, let the user pick a rank, re-run with `chosen_rank` to generate
    
    ## Related skills
    
    | When to hand off | Skill |
    | --- | --- |
    | Mining comments from YouTube videos for customer research | [`customer-research`](../customer-research) |
    | Finding top YouTube videos by topic | Use `youtube_videos_search_top` directly |
    | Generating video content | [`video-generation`](../video-generation) |
    
    For title options, descriptions and thumbnail concepts, run `generate_seo_titles.py`, `generate_seo_description.py` and `analyze_thumbnail_concepts.py` under `scripts/`. They call `ai_functions_run` from the sandbox using fetched transcript and video context. Follow the output contracts in `references/packaging-schemas.json`; the thumbnail reference explains the workflow.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related