← Plugin catalog
Productivity
Wistia
Wistia v3.0.0
Publisher description
From the marketplace listing
Wistia's MCP server lets you manage your entire video library from ChatGPT. Upload videos, make bulk updates, manage content access, and check webinar or video performance with quick prompts where you already work. Built-in skills enable you to automate your video and webinar workflows at scale that drive real business growth.
Language: English · Automatically detected from descriptions.
Files & skills
File archives
Plugin package15 files · 219 KBBrowse files →
Skill instructions
wistia-engagement-tools11.7 KB
---
name: wistia-engagement-tools
description: >
Audits a Wistia video's engagement (play rate, avg view time, drop-off point,
re-watched segments), classifies its type and goal, and recommends where to add or
move an interactive feature — lead capture form, CTA, or annotation link — using
Wistia's State of Video benchmark data. Use whenever the user asks to "audit
engagement," wants to know "where should I put a CTA / lead capture form /
annotation link," asks "why are people dropping off this video," wants to "improve
conversion," or references an "engagement audit/report/tools" workflow. Applies to
videos meant to drive leads, conversion, or engagement, with 10+ plays (fewer is a
caveat, not a blocker). Also trigger for a bulk/account-wide version ("audit
engagement across my videos," "which videos need a CTA"). Do NOT trigger for general
analytics questions with no engagement-feature angle — use Wistia tools directly.
---
# Wistia Engagement Tools
## Welcome message
Show this to the user before starting the workflow:
> Let's see how your video is really being watched: play rate, where people drop off, what they rewatch. Then I'll recommend where a lead capture form, call-to-action (CTA), or annotation link would do the most good.
>
> Point me to the video (or say "all videos"). Works best at 10+ plays; below that, I'll just flag it as a caveat, not a dealbreaker.
>
> You'll get a placement recommendation, backed by that video's own data and Wistia's benchmarks.
---
Diagnoses how a video is actually being watched, then recommends where to place a lead
capture form, CTA, or annotation link based on that video's own drop-off/re-watch data
*and* Wistia's State of Video benchmarks for videos like it.
All Wistia actions use the Wistia MCP tools — call `tool_search` for the relevant tool
family before using it, since these are deferred tools and their exact parameters aren't
in context by default. Read `references/state-of-video-benchmarks.md` before Step 4 —
it has the exact tables this skill's recommendations are built from; don't try to recall
the numbers from memory. `assets/retention-chart-template.html` has the retention-curve
chart used in Step 5 — read it when you get there rather than rebuilding the chart from
scratch each time.
**This skill can modify a live video's engagement features in Step 6.** Never call an
`update-*-customizations` tool without explicit, per-change confirmation — see Step 6.
---
## Step 0: Scope — single video or bulk scan
Default to a single video (the user names it, pastes a URL, or you're mid-conversation
about a specific one). If the user asks for an account-wide or multi-video audit
instead:
1. Ask for a date range (default: last 90 days) and how many candidate videos to
consider (default: top 25 by plays).
2. Call `show-account-top-content` with `group_by: media`, `sort_by: plays`, the date
range, and that `per_page`.
3. Run Steps 1–5 for each video that clears the qualification bar (below). Skip Step 6
(applying changes) in a loop — surface all recommendations together at the end and
let the user pick which video(s) to act on, then apply one at a time per Step 6.
4. Bulk runs default to a saved artifact (see Step 7) rather than a chat report — there's
no need to ask when there's more than one video.
For a single video, continue below directly.
---
## Step 1: Qualify the video
Confirm the video is meant to drive leads, conversion, or engagement — if that's not
obvious from context, ask the user rather than assuming. This skill isn't useful for
purely informational or internal-only videos with no engagement goal.
Check play count via `show-media-aggregated-stats`. **10 plays is a soft floor, not a
hard gate**: below it, continue the audit but flag prominently in the report that the
sample size is small and the recommendation is lower-confidence.
---
## Step 2: Pull current engagement data
For the target video (`mediaId` = hashed ID), gather:
- **Play rate, average view time (percent watched), plays** — `show-media-aggregated-stats`
and/or `show-media-analytics` (needs `start_date`/`end_date`; default to all-time or
last 90 days if the user has no preference — ask if ambiguous).
- **Drop-off point and re-watched segments** — `show-media-engagement`. This returns a
per-second (or per-bucket) retention curve:
- **Drop-off time** = the point where the retention curve falls off sharply (not just
the very end — look for the steepest sustained decline, which is often earlier than
where the curve finally bottoms out).
- **Drop-off rate** = the percentage of viewers lost by that point (100% minus
retention at that timestamp).
- **Re-watched areas** = any segment where the curve rises or plateaus above the
surrounding trend (viewers replaying that section) — report timestamp ranges, not
just "yes/no."
Report these five figures plainly before moving on:
1. Play rate
2. Average view time
3. Drop-off rate
4. Drop-off time
5. Re-watched areas (if any — it's fine to report "none detected")
---
## Step 3: Classify type and goal, confirm with the user
Determine the video's likely type from two sources:
- **Metadata** — title, description, folder/project name via `get-medias` or the
media's existing details.
- **Transcript** — pull captions via `get-captions` (`media_id` = the video's hashed
ID) and skim for content cues (e.g. "welcome to our webinar," "here's how to set
up...," testimonial-style first-person praise).
Map your best guess to one of the **11 fixed video types** in the reference file (don't
invent new categories — pick the closest fit). Then ask the user to confirm, using
`ask_user_input_v0` where available:
1. **Video type** — present your best guess plus 2-3 plausible alternatives from the
fixed taxonomy.
2. **Goal** — Lead generation / Conversion (drive action) / Engagement (authority-
building), defaulting to your best guess based on type (e.g. testimonials often lean
conversion, tutorials often lean engagement) but let the user override.
Don't proceed to Step 4 with an unconfirmed type/goal — the recommendation logic depends
on both.
---
## Step 4: Recommend placement
Read `references/state-of-video-benchmarks.md` now if you haven't already this session.
1. Determine the video's **length bucket** (<1 min, 1-3, 3-5, 5-30, 30-60, 60+).
2. Look up **expected engagement rate** for this video's (type, length) cell in table 1.
Compare to the video's actual engagement rate from Step 2 — say plainly whether it's
under/over/in-line with the benchmark.
3. Map the confirmed **goal** to a feature: Lead generation → lead gen form, Conversion
→ CTA, Engagement → annotation link.
4. Use table 6 (lead gen form click-through by placement × length) to find the
best-performing placement row for this video's length bucket. Apply this table to
whichever feature was chosen in step 3, and say explicitly that table 6 is built from
lead-gen-form data being used as a proxy when the feature isn't a lead gen form.
5. Cross-check against the video's **own** drop-off point from Step 2 — if the
benchmark-recommended placement lands after the video's typical drop-off point, flag
this tension explicitly rather than silently picking one. In that case recommend
placing the feature at or just before the drop-off point instead, and explain why
you're overriding the raw benchmark.
6. For CTAs specifically, also sanity-check against the 2025 qualitative rules (table 7)
as a tiebreaker.
7. If there's a re-watched segment from Step 2, call it out as a secondary candidate
spot for an annotation link regardless of the primary recommendation — high
re-watch usually means high interest, which is exactly where a clickable link
performs best.
State the recommendation as a specific timestamp or range (e.g. "around 4:10, in the
video's 4th quarter"), not just a vague zone.
---
## Step 5: Report
Render the retention curve as an inline chart before the text summary. Call
`visualize:read_me` with `modules: ["chart"]` if you haven't loaded it this
session, then use `assets/retention-chart-template.html` as the base for
`visualize:show_widget` — fill in its placeholders from the `engagement_data`
array (Step 2), the video duration, any re-watch zones, and the recommended
placement from Step 4. Skip the recommendation marker only if Step 4 hasn't
run yet (e.g. reporting mid-workflow before type/goal confirmation). Don't
repeat the chart's content in your prose afterward — narrate the
recommendation and reasoning, not the numbers already on screen.
Single video, chat report by default:
```
📹 [Video title] — [type] · [goal] · [length bucket]
📊 Plays: [N] [⚠️ below 10 — low-confidence flag if applicable]
▶️ Play rate: [X]%
⏱️ Avg view time: [X]% watched
📉 Drop-off: [X]% of viewers by [timestamp]
🔁 Re-watched: [timestamp range(s), or "none detected"]
Benchmark comparison: [actual]% vs [expected]% engagement for [type] at this length
([above/below/in line with] benchmark)
💡 Recommendation: Add a [feature] at [timestamp] ([placement label], based on [X]%
click-through for [length bucket] videos at that position)
[Secondary note on re-watched segment, if any]
[Note on any tension between benchmark placement and actual drop-off point, if any]
```
After the report, offer to save it as an artifact even for a single video (per Step 0,
artifacts are the default for bulk but always optional for single-video runs — just
ask).
---
## Step 6: Apply (only with explicit per-change confirmation)
If the user wants to act on the recommendation, confirm the exact change before calling
anything — restate feature, timestamp/placement, and any text/URL needed, and get a
clear yes. Never batch-apply without confirming each one, even in a bulk run.
- **Lead gen form**: `update-lead-capture-customizations` — `provider: wistia_form` (or
the account's CRM provider if they use HubSpot/Marketo/Pardot), `settings.time` set to
the recommended placement.
- **CTA**: `update-engagement-customizations` with `plugin.postRoll-v1` — set `on: true`,
`time` to the recommended placement (or `"end"`), plus `text`/`link` from the user.
- **Annotation link**: `update-engagement-customizations` with `plugin.midrollLink-v1` —
set `on: true` and add an entry to `links` with `time`, `text`, `url`, `duration`.
After applying, confirm success and remind the user these changes are live immediately.
---
## Handling gaps and edge cases
- **Video has no transcript** → classify from metadata alone, flag lower confidence on
the type guess, still ask the user to confirm.
- **`show-media-engagement` returns sparse/noisy data** (very low plays) → say so
plainly rather than inventing a precise drop-off timestamp; give a rough zone instead
("somewhere in the first third") and lean harder on the play-count caveat from Step 1.
- **Video doesn't fit any of the 11 types well** → pick the closest and say so
explicitly when confirming with the user, rather than silently forcing a bad fit.
- **User's stated goal doesn't match the feature they explicitly asked for** (e.g. goal
is "lead generation" but they asked specifically about annotation links) → go with
what the user explicitly asked for, but note the mismatch and mention the benchmark
case for the goal-aligned feature instead.
- **Video already has an engagement feature in place** → check current state via
`show-lead-capture-customizations` / the engagement customizations before
recommending — recommend *moving* it if placement is suboptimal rather than assuming
none exists.
- **Bulk run with no qualifying videos** (none meet the "drive leads/conversion/
engagement" intent) → say so, don't force recommendations onto videos with no
engagement goal.
Referenced files: 2
wistia-language-audit9.74 KB
---
name: wistia-language-audit
description: >
Audits which languages the audience of Wistia's top-performing videos actually watches
in, checks whether transcript translations (captions/subtitles) and video dubs already
exist in those languages, and reports the gaps ranked by measured audience demand. Use
this skill whenever the user asks for a "language audit," wants to know "what languages
should I dub/translate my videos into," asks to "audit my video languages," "find
missing translations/dubs," or anything about matching video localization to viewer
language demand. Do NOT trigger for one-off single-video captioning/dubbing requests
with no audience-analysis component — use the Wistia tools directly for those.
---
# Wistia Language Audit
## Welcome message
Show this to the user before starting the workflow:
> Let's find out what languages your viewers actually speak, and where your captions and dubs have gaps.
>
> Nothing to prepare. I'll pull your analytics and existing translations myself.
>
> You'll get a coverage report showing exactly which translations and dubs are missing, ranked by real audience demand.
---
Finds the account's top-performing videos, figures out what languages their viewers
actually speak (from analytics, not guesses), and audits existing transcript translations
and dubs against that language demand.
All Wistia actions use the Wistia MCP tools — call `tool_search` for the relevant tool
name before each new tool family, since these are deferred tools and their exact
parameters aren't in context by default. Read `references/language-codes.md` before Step
3 — the analytics, captions, and dub tools use different language code formats and this
skill fails silently (comparing the wrong codes) if you skip that step.
**This skill is read-only.** It audits and reports; it never creates translations or
dubs or makes any other changes to the account. Filling the gaps is done by the user in
the Wistia app.
---
## Step 1: Parameters (ask, don't assume)
Ask the user, using `ask_user_input_v0` where helpful:
1. **Date range** for "top performing" and "audience language" — default to last 90 days
if they have no preference, but confirm.
2. **How many top videos** (N) to audit — e.g. top 5 / top 10 / top 20.
3. **How many top languages** (or a minimum share threshold, e.g. "languages with ≥5% of
plays") to target per the aggregate audience — this determines what counts as a "gap."
Top videos are ranked by **plays, tie-broken by engagement rate** (the account's
established default — don't re-ask this each run unless the user wants to change it).
---
## Step 2: Identify top videos with a transcript
Only consider videos that already have a transcript (i.e. at least one caption track,
almost always the source-language one auto-generated on upload). This is a deliberate
filter, not an optimization to skip — Wistia accounts reliably surface non-content media at
the top of raw play counts: UI loop assets, "don't delete" test/synthetic media, silent
B-roll, short onboarding-flow snippets. None of that is a sensible localization candidate,
and a transcript is the cheapest reliable proxy for "this is spoken content someone should
watch in their own language." Do this filtering **before** ranking, not as a post-hoc
cleanup step — it changes which N videos qualify, not just how they're displayed.
1. Call `show-account-top-content` with `group_by: media`, `sort_by: plays`,
`sort_direction: desc`, the chosen date range, and a generous `per_page` (150–200) to
get a large candidate pool — the qualifying N will be a subset of this, and how large a
subset varies a lot by account.
2. Walk the candidates in ranked order and check each one for an existing transcript via
`get-captions` with `media_id` set to that candidate's hashed_id. `returned_count > 0`
means it has a transcript; qualifies. `returned_count == 0` means no transcript; skip it
without spending further calls on it.
- Note: `get-captions` returns full caption text per language, not just a summary — this
is unavoidable but fine; you only need `returned_count` from each response, don't
dwell on the text.
- Do **not** use the account-wide `get-captions` call (omitting `media_id`) to try to
shortcut this — it returns full SRT text for every caption track on the account with
no media-linking field, so it can't be matched back to a specific video and is far
more expensive than checking candidates one at a time.
3. Stop once you have N qualifying videos. If there are ties on plays at the cutoff among
qualifying videos, break them using `engagement_rate` from the same top-content
response.
4. This can take a lot of individual `get-captions` calls on accounts with a lot of
non-content media mixed into top plays (dozens, sometimes) — that's expected, not a
sign something's wrong. Keep going without narrating each check; report the final
qualifying list, not the rejected candidates.
Keep the full analytics row per qualifying video (plays, engagement_rate, played_time,
media_duration) — you'll want it in the final report, and `media_duration` feeds the
report's duration column directly (skip the separate `get-medias` duration lookup in Step
4 when this is already present; only fall back to `get-medias` if `media_duration` came
back null).
---
## Step 3: Identify top audience languages
For each of the N videos, call `show-media-languages` over the same date range. This
returns viewer plays broken down by browser language.
- **Normalize codes first** — see `references/language-codes.md`. Collapse regional
variants (e.g. `es-MX`, `es-ES`) to the base language unless a variant has meaningfully
distinct volume and the user cares about the distinction.
- **Aggregate** play counts per language across all N videos to get one account-wide
ranked list of audience languages.
- Apply the threshold/count from Step 1 to get the **target language set** — the
languages this audit will check every top video against.
Always exclude the video's own source/original language from the target set (no point
"translating" a video into the language it's already in).
---
## Step 4: Audit existing coverage
For each of the N videos, against the target language set:
- Call `get-captions` (filtered to that `media_id`) to see which transcript/caption
languages already exist.
- Call `gets-localizations` for that `mediaHashedId` to see which dubbed languages already
exist.
- Map each result's 3-letter code back to a language name using the reference table, and
mark each (video × target language) cell as:
- ✅ have both transcript translation + dub
- 📝 transcript translation only, dub missing
- 🎙️ dub only, transcript translation missing (unusual, but possible)
- ❌ have neither — both needed
Duration for the report should already be in hand from Step 2's `media_duration` field
(seconds). Only call `get-medias` here if that came back null for a particular video —
flag it as a fallback lookup, not the default path.
---
## Step 5: Report the gaps
**Present both a targeted and a full-coverage gap list, and recommend targeted.**
Applying the account-wide target language set uniformly to every video (full coverage)
routinely produces a materially larger, wasteful list — a video with zero measured plays
in a target language still gets flagged for a dub in it. Targeted means: only flag a
(video, language) dub gap where that specific video shows meaningful demand for that
language (default threshold: ≥5 plays over the date range; mention the threshold used and
let the user move it). Show both lists so the user can see the difference and pick, but
lead with targeted as the default recommendation. Transcript translation gaps stay listed
for any language with >0 plays regardless of threshold — there's no reason to be
conservative there.
Flag videos with **zero plays across every target language** separately and recommend
deprioritizing them, even if they're missing every transcript/dub — being missing isn't
the same as being wanted; a video with no demonstrated audience in any target language
has nothing to localize into yet.
Present a clear table: video title | duration | language | what's missing | plays in that
language. Rank by plays in the missing language so the biggest gaps lead.
---
## Step 6: Final summary
Recap what was found:
```
📊 Audit: top [N] videos, [date range], target languages: [list]
🌍 Coverage gaps found: [X transcript translations, Y dubs]
🎯 Recommended first: [the targeted gaps with the most measured demand]
❓ Flagged: [any videos with unknown duration, ambiguous codes, or no demand in any target language]
```
Note that transcript translations, dubs, and captions are added from the Wistia app, and
that the audit can be re-run after changes to confirm the gaps are closed.
---
## Handling gaps and edge cases
- **Video has no clear source language** (e.g. no primary transcript) → note it in the
audit rather than guessing.
- **Ambiguous language code** not in the reference table → look it up, state the mapping
you used in the audit output, don't silently assume.
- **User wants to re-run with different N/date range/threshold** → just re-run Steps 1–5.
- **Duration unavailable for a video** → show its duration as unknown in the report,
don't guess a number.
- **Very few or no videos qualify with a transcript** → say so plainly rather than quietly
lowering N or substituting non-transcript videos; ask whether to widen the candidate pool
(larger `per_page` in Step 2) or proceed with fewer than N videos.
- **A qualifying video has a transcript but shows zero plays in every target language** →
keep it in the audit for completeness but exclude it from the recommended list
(see Step 5); don't silently drop it from the report either.
Referenced files: 1
wistia-registration-updates5.31 KB
---
name: wistia-registration-updates
description: Track registration pacing for a Wistia webinar against its goal and deliver scheduled updates (weekly, ramping to daily the week of the event, no weekends). This is STEP 2 of a three-skill webinar workflow (create → registration updates → post-event recap). Use when setting up or running pre-event registration reporting, checking how signups are pacing vs goal, flagging low registration or hot leads, or when handed off from webinar-event-creation. When the event ends it hands off to the post-event recap skill.
---
# Webinar Registration Updates (Step 2 of 3)
Answer the recurring promo-phase question: **are we going to hit our registration goal, and if not, how far off are we?** Pulls live registration data, compares to the goal, and delivers a short, scannable update — as a **scheduled task in Claude** that ramps in frequency as the event nears.
Middle of three sequential skills: `webinar-event-creation` → **this** → `wistia-webinar-recap`. Coordinates via **`webinar-state.json`** in the working folder.
## Identify the webinar
Read `webinar-state.json` for the active webinar (`webinar_hashed_id`, `goal`, `scheduled_for_utc`, `time_zone`). If there's no state file (Sam started here), auto-detect the soonest **upcoming published** webinar via `get-webinars` (`registration_status == published`, `scheduled_for` in the future, not `ended`, excluding test titles), confirm with Sam, and write `webinar-state.json`. Set `stage: "promoting"`, `reporting_scheduled: true` once the schedule is set.
Remember the timezone quirk: `scheduled_for` clock time is local to `time_zone`, not UTC.
## Pull the numbers (Wistia MCP)
- `show-webinar-analytics` → current total `registrations`, `impressions`.
- `show-webinar-registration-timeseries` (`granularity: daily`) → daily series for run-rate, trend, and spikes. Average the last ~7 non-empty days; registration is spiky, so don't trust a single day.
- `show-webinar-audience` (recent registrants, ~1 page) → for the Notes: tally `referrer_domain`/`utm_source` (new sources), scan `company`/email domain (notable logos), flag repeat-visit or enterprise/target-account registrants (hot leads). Keep it light — don't paginate the whole list.
## Pacing math
`current`, `goal`, `days_left` (to `scheduled_for`), `rate` (recent daily run-rate).
- **% to goal** = current / goal
- **Required run-rate** = (goal − current) / days_left — compare to actual `rate` (most useful single number)
- **Projected final** = current + rate × days_left → on track (≥100%), slightly behind (85–99%), or **behind / "low reg"** (<85%, surface prominently with a lever: extra email, paid, partner promo).
**Known caveat (projection parked):** the trailing-average `rate` is inflated by email-send spikes, so projections overshoot right after a send (a mid-campaign test projected ~554; actual was 469). Planned fix: report projection as a range (organic-baseline floor vs recent-average ceiling). Until then, lead with the **% to goal** and **required vs actual rate**, and treat the single projected number cautiously.
## Output format (required)
```
<Webinar title>
Date: MM/DD/YY | <N> Days till Showtime
Goal: <goal> Registrations
Current: <current> Registrations | <%> of Goal
Notes:
• <spikes — biggest recent day + the promo that likely drove it>
• <new sources of registration>
• <notable logos among recent registrants>
• <hot leads to flag for sales — repeat visits, enterprise/target accounts>
```
The header block is fixed; **Notes** carry the value — include only bullets with something real to say. Don't invent data; if nothing notable since last run, write "Steady period — nothing notable to flag."
## Delivery & cadence (scheduled task)
These are delivered as a **scheduled task in Claude**, anchored to the event date:
- Until the week of the webinar: **weekly**.
- The week of (≤ 7 days out): **daily**.
- **No weekend updates.** After the event: stop and hand off to step 3.
Implement with **one weekday task** (`0 9 * * 1-5` — 9am local, Mon–Fri) and gate inside the prompt by `days_until`:
- `days_until > 7`: weekly phase — only post on **Mondays**; otherwise exit quietly.
- `0 ≤ days_until ≤ 7`: daily phase — post every weekday.
- `days_until < 0`: event over — see *Handoff* below.
The scheduled task's prompt must be **self-contained** (fresh run, no memory): include the webinar's hashed ID, goal, event date, which Wistia tools to call, the gating rule, and the exact output format. Tell Sam the cadence you set so he can adjust or pause it, and suggest he click "Run now" once to pre-approve the Wistia tools so unattended runs don't stall on permissions.
## Handoff → Step 3
When a scheduled run finds the event has passed (`days_until < 0`):
1. Post a brief final note ("Registration closed at <final> / <goal> — <%>").
2. Set `webinar-state.json` `stage: "recapped_pending"` and recommend **disabling this scheduled task**.
3. Prompt Sam to run **`wistia-webinar-recap`** for the performance breakdown (or, if appropriate, invoke it directly).
So the sequence self-advances: the last pre-event run is also the trigger to move to the recap.
## Not in scope
Hot-lead alerting to sales as its own workflow, and CRM/pipeline reporting, are intentionally out of scope (Stage 4 dropped). Hot leads surface only as a Notes bullet here.
wistia-video-accessibility-audit7.57 KB
---
name: wistia-video-accessibility-audit
description: Audit a Wistia account's most-seen videos for accessibility — captions, multi-language subtitles, AI dubbing, and audio descriptions — using the account's own audience data, and report the gaps ranked by audience impact. Use when the user asks for an accessibility audit, caption/subtitle coverage check, or wants to find accessibility gaps in their Wistia videos.
author: Wistia
version: 1.0.0
---
# Wistia Video Accessibility Audit
A portable AI skill that audits a Wistia account's most-seen videos for accessibility — captions, multi-language subtitles, AI dubbing, and audio descriptions — using the account's own audience data, and reports the gaps ranked by audience impact.
**Works with any LLM that supports the Wistia MCP connector** (Claude, ChatGPT, Cursor, or any MCP-capable client). To use: connect the Wistia MCP, then paste this file as a system prompt, project instructions, custom agent instructions, or install it as a skill. No other dependencies.
---
## First run — welcome message
If this is the user's first audit (no saved site profile from a previous run), greet them with this message verbatim and wait for answers:
> 👋 **Welcome to Wistia's Video Accessibility Audit**
>
> In Wistia's 2026 State of Video report, **71% of teams caption their videos — but only 23% subtitle in multiple languages**, leaving most audiences behind. This skill finds your most-seen videos and spots the caption, dubbing, and audio description gaps using your own audience data.
>
> Two quick things before your first audit:
>
> 1. **What's your website URL?** (e.g. yourcompany.com — so I can tell your pages apart from third-party embeds and group videos by the pages they live on)
> 2. **Any pages you'd call high-value?** (homepage, product, pricing — or just say "you pick" and I'll use traffic + common patterns)
Remember the answers (save to a state file if your environment supports files; otherwise carry them in conversation) so they're never asked twice. Use the domain for the owned-page rule below.
## Why this matters (context for the output)
- 71% of teams add closed captions; 69% use AI captioning; 36% add on-screen transcripts; only 23% add subtitles in multiple languages (Wistia 2026 State of Video).
- Top localization languages, in order: Spanish, French, German, Japanese, Portuguese.
- AI users are 50% more likely to use audio descriptions; the European Accessibility Act is raising the legal stakes.
- Captions also drive engagement (sound-off viewing) and SEO (transcripts are indexable).
## Workflow
### 1. Scope: top videos by impressions
Rank by **impressions (player loads), not plays** — accessibility is about who *sees* the player, and a homepage video with a 2% play rate can have 30× more impressions than plays.
- Call `show-account-top-content` (trailing 30 days, `sort_by: plays`, `per_page: 100`, `group_by: media`) — each record includes **`unique_loads`**. Re-rank by that locally and take the top 20. (The API can't sort by loads; the wide pull guards against high-load/low-play videos hiding below the plays cutoff.)
- Filter out test/synthetic media (names containing: synthetic, playwright, test, "don't delete").
### 2. Exclude videos with no speech
Silent loops can't be captioned or dubbed; including them creates false "missing captions" alarms. There's no has-audio flag in the API, so detect in layers:
1. **Has caption tracks → has speech.** Include.
2. **No captions + loop signals → assume silent. Exclude.** Signals: name or folder/subfolder/section contains loop/looping/header/banner/b-roll/time lapse/montage/background (check via `get-medias` with `hashed_ids` — the `subfolder.name` field is often the giveaway, e.g. "Home Looping Videos"), or duration under ~10 seconds.
3. **No captions + no loop signals → ambiguous. Don't guess** — ask the user to confirm with a quick listen before scoring it as a caption gap.
### 3. Find each video's hosting page
- Per video: `show-media-embed-locations` (**trailing ~90-day window** — longer ranges can return empty) → `embed_url`, `page_title`, plays per page. Skip localhost, `127.0.0.1`, `*.vercel.app`, staging/preview domains, and the Wistia app itself.
- **Only include videos hosted on a page the user controls** (matching their website domain). Videos with no embeds or only third-party/customer embeds are excluded from the tables — but if one carries a big insight (high impressions, big language gap), mention it in one line below the tables.
### 4. Pull accessibility status per video
| Check | Tool | Extract |
|---|---|---|
| Captions + languages | `get-captions` with `media_id` | language per track only — **ignore the `text` field** |
| Dubbed versions | `gets-localizations` | language, enabled |
| Player settings | `show-accessibility-customizations` | captions on, audio description control, extended audio description |
| Audience language demand | `show-media-languages` (trailing ~6 months) | viewer language, % of plays, `captions_support` flag |
Account-wide, once: `get-media-extended-audio-descriptions` (`per_page: 100`) → which media have audio description tracks.
A video's **uncovered languages** = viewer browser languages ≥2% of plays with no matching caption track or dub. If `captions_support: false` for a language (e.g. Chinese), captions can't cover it — dubbing is the only fix; say so.
### 5. Deliver in chat
Markdown tables directly in the chat response — no HTML file, no Slack formatting.
- **Group by page type** of the hosting page, one small table per group with a bold heading: Homepage, Product pages, Demo, Learn, Explore, Series (adapt groups to the site's actual structure).
- **Columns**: Video | Hosted on | Impressions (30d) | Captions | Dub | Audio desc. | Uncovered languages (with percentages, ⚠️ for big gaps)
- **Both link columns clickable**: Video → its Wistia media page (`https://<account-subdomain>.wistia.com/medias/<hashed_id>`; get the account URL from `get-current-account`); Hosted on → the full page URL (display without the scheme).
- Fully covered videos still get listed — they show the playbook works.
- **Summary line below the tables**: totals for gaps found (videos missing captions, uncovered languages, missing audio descriptions). Close with the bright spot (e.g. "every talking video already has English captions").
- Formatting: blank line before every table and list (CommonMark).
### 6. Recommend priorities
Close by recommending which gaps to tackle first: the languages with the biggest uncovered viewer share on the highest-impression pages. Recommend languages from each video's own audience data first, then the report top-5 (es, fr, de, ja, pt). Captions, caption translations, dubs, audio descriptions, and player accessibility settings are all managed from the Wistia app — this skill reports and recommends only; it does not make changes to the account.
## API pitfalls (learned from live runs)
- **Never call `get-captions` without `media_id`** — account-wide it returns every caption file's full text (millions of characters on large accounts). Even per-media, read only the language fields.
- **Embed-location endpoints are flaky over long ranges.** Use ~90 days; an empty result means "no recent embed data," not "not embedded."
- **Don't paginate the whole library** on large accounts (10k+ media) — scope via top-content.
- **`show-media-languages`** is viewer *browser* language (demand), not content language.
- **`show-accessibility-customizations`** returns strings ("true"/"false"), not booleans.
- **No has-audio flag exists** anywhere — hence the layered speech heuristic in step 2.
wistia-video-performance-report10.6 KB
---
name: wistia-video-performance-report
description: >
Builds a video performance report for a set of Wistia videos — a single video, a
channel, a folder, or every video embedded at a shared location — comparing actual
performance against both this account's own internal average and Wistia's State of
Video report benchmarks, then produces a chart and a stylized downloadable report with
insights and recommendations. Use whenever the user asks for a "performance report,"
"performance review," "performance dashboard," wants to know "how are my videos
performing," "how's this video doing," "how does this compare to benchmark," asks to
"audit performance across a channel/folder," or references comparing videos to the
State of Video report. Also trigger for "compare these videos" or "which of my videos
is underperforming" type requests. Do NOT trigger for single-video engagement/CTA
placement questions with no reporting angle — use wistia-engagement-tools for those
instead (this skill can hand off to it for flagged videos in Step 7).
---
# Wistia Video Performance Report
Produces an account-aware performance report: pulls real stats for the requested videos,
benchmarks them against both the account's own historical average (internal) and
Wistia's State of Video report (external), then renders a comparison chart and a
polished, savable report.
All Wistia actions use the Wistia MCP tools — call `tool_search` for the relevant tool
family before using it, since these are deferred tools and their exact parameters aren't
in context by default.
This skill reuses the benchmark data already bundled with the `wistia-engagement-tools`
skill rather than duplicating it — read
`/mnt/skills/user/wistia-engagement-tools/references/state-of-video-benchmarks.md` in
Step 4. **If that file is present, never ask the user to re-upload the State of Video
report** — it's already available. Only ask them to provide it if that file is genuinely
missing.
---
## Step 0: Welcome message
Open with a short welcome line in Wistia's voice before asking anything — warm, direct,
conversational, encouraging. Not hype-y, not dry, no corporate throat-clearing ("We're
excited to..."). Plain verbs, no filler. One or two sentences, then move straight into
the scope question in Step 1 — don't make the welcome its own separate turn.
Examples of the tone (write your own, don't reuse verbatim):
- "Let's see how your videos are doing. First, tell me what you'd like to look at."
- "Happy to pull a performance report together. Which videos should I look at?"
Avoid: "I'd be delighted to assist you with a comprehensive analysis..." (too stiff) and
"🎉 Let's dive into your amazing video stats!" (too hype-y — also no emoji per house
style elsewhere in these skills).
---
## Step 1: Scope — which videos
Ask using `ask_user_input_v0`:
**Question:** "What would you like to analyze?"
**Options:** "A single video", "A channel", "A folder", "Videos at a shared embed
location"
Resolve into a concrete list of `hashed_id`s before moving on:
- **Single video** — ask which one (name, URL, or hashed_id) if not already given.
Resolve via `get-medias` or `Wistia:search`.
- **Channel** — ask which channel if there's more than one (`get-channels`), then list
its videos with `get-channel-episodes`.
- **Folder** — ask which folder if there's more than one (`get-folders`), then list its
videos with `get-medias` filtered to that folder.
- **Shared embed location** — ask the user for the domain or page URL. Check
`show-account-embed-locations` for a matching location and its top content. If the
tool's response doesn't break the location down to individual media (granularity
varies), say so plainly and ask the user to confirm the specific video titles instead
of guessing — don't silently narrow to "whatever look right."
If a scope resolves to an unusually large number of videos (30+), tell the user the
count and confirm they want the full set before pulling stats for all of them — offer to
narrow to top-N by plays instead, since a 30-video report is unwieldy to read.
---
## Step 2: Timeframe
Ask using `ask_user_input_v0`:
**Question:** "What time period should the report cover?"
**Options:** "Last 7 days", "Last 30 days", "Last 90 days", "Custom range"
If "Custom range," ask directly in chat for start and end dates (free text — the
elicitation tool only supports fixed options). Convert the choice to `start_date`/
`end_date` values for the analytics calls in Step 3.
---
## Step 3: Pull performance data
For each video (`mediaId` = hashed ID):
- **Actual stats** — `show-media-aggregated-stats` and/or `show-media-analytics` for the
chosen date range: plays, play rate, engagement rate (% watched), average view time,
and any conversions if a lead capture form or CTA is active.
- **Length and type** — pull duration and metadata via `get-medias`. Classify the video's
type using the same fixed 11-type taxonomy as `wistia-engagement-tools` (see the
benchmarks reference file for the list) — best-guess from title/description/folder
name, no need to stop and confirm with the user for a report (unlike the engagement
skill, this isn't modifying anything live). Note the guess plainly in the report rather
than presenting it as certain if it's a stretch.
- **Length bucket** — map duration to one of: `<1 min`, `1-3 mins`, `3-5 mins`,
`5-30 mins`, `30-60 mins`, `60+ mins`.
Then compute the two benchmarks:
- **Internal benchmark** — this account's own average over the same date range, across
videos *outside* the requested scope where possible (so a video isn't partly
benchmarked against itself). Use `show-account-top-content` (or
`show-current-account-stats` / `show-account-analytics` if it exposes the same
figures) for the date range to compute an average play rate and engagement rate. If
the account has too few other videos to form a meaningful average (e.g. fewer than 5),
say so and lean more heavily on the external benchmark in the narrative.
- **External benchmark** — read
`/mnt/skills/user/wistia-engagement-tools/references/state-of-video-benchmarks.md` and
look up table 1 (engagement rate by type × length) and table 2 (play rate by length)
for each video's own (type, length) cell.
Report all three figures (actual / internal / external) per video plainly before moving
to the chart — this is what Step 5's chart and Step 6's report are built from.
---
## Step 4: Read the benchmarks reference
If you haven't already this session, read
`/mnt/skills/user/wistia-engagement-tools/references/state-of-video-benchmarks.md` now.
Don't try to recall the benchmark numbers from memory — they're specific and the whole
point is comparing against the real published figures.
---
## Step 5: Chart
Call `visualize:read_me` with `modules: ["chart"]` if you haven't loaded it this session,
then use `assets/performance-comparison-chart-template.html` as the base for
`visualize:show_widget`. Fill in the video labels and the actual/internal/external arrays
for both engagement rate and play rate from Step 3 — the template ships a toggle between
the two metrics so both need real data, even if your prose below leads with one.
Don't repeat the chart's numbers in your prose afterward — narrate the story (who's
ahead of benchmark, who's behind, and roughly by how much), not a recitation of every
figure already visible on screen.
---
## Step 6: Stylized report
Build the downloadable report as a real `.html` file (not a `visualize` widget — this is
a saved deliverable). Use `assets/report-template.html` as the base:
1. Copy its structure, fill every `{{PLACEHOLDER}}`.
2. Duplicate the video-card block once per video in scope, filling it fresh each time.
3. Write the "Insights & recommendations" section fresh — 3-6 bullets, each naming a
specific video, its benchmark gap (internal and/or external, whichever is more
striking), and one concrete next step. Ground every claim in a number that also
appears elsewhere on the page. No generic advice that could apply to any account (see
the template's own comments for what "specific" looks like).
4. Pick badge classes (`up`/`down`/`flat`) per the template's own guidance — don't assume
higher is always better for every metric.
5. Save to `/mnt/user-data/outputs/` and share it with `present_files`.
For a **single video**, this report is still worth generating (it's a nicely formatted
one-card version) — but ask first whether the user wants the saved file or just the chat
summary, since a single-video report is optional overhead. For **multiple videos**,
default to producing the file without asking, since that's the point of a report.
---
## Step 7: Wrap-up and handoff
After sharing the report:
- If any video is meaningfully behind both benchmarks (say, more than ~10 points below
on engagement rate), mention that `wistia-engagement-tools` can do a deeper dive on
that specific video — where to place a CTA or lead capture form based on its own
drop-off data — rather than duplicating that analysis here.
- Don't proactively offer to re-run the report on a different timeframe or scope unless
the user seems like they want to iterate; a simple "let me know if you'd like this for
a different set of videos or time period" is enough.
---
## Handling gaps and edge cases
- **Video has too few plays to be meaningful** (rule of thumb: under 10) — still include
it in the report, but flag the low sample size prominently next to its numbers rather
than silently reporting a noisy percentage as if it were solid.
- **Video doesn't fit any of the 11 fixed types well** — pick the closest and say so in
the report rather than forcing a bad fit silently.
- **Folder/channel/embed location resolves to zero videos** — say so plainly, don't
fabricate a report from nothing, and ask the user to double check the name or try a
different scope.
- **Account has no other videos to build an internal benchmark from** (new account, or
the requested scope *is* the whole account) — say so, drop the internal-benchmark
column/badge for that report, and rely on the external State of Video benchmark alone.
- **State of Video benchmarks reference file is missing** — this would only happen if
`wistia-engagement-tools` isn't installed or its reference file moved. In that case,
tell the user you don't have the benchmark data bundled and ask them to provide the
State of Video report (don't guess numbers from memory — these are specific published
figures).
- **Custom date range longer than the account's data history** — clamp to whatever data
actually exists and say so, rather than silently returning a partial-period average
labeled as the full requested range.
wistia-video-upload6.72 KB
---
name: wistia-video-upload
description: >
Runs the standard set of setup steps for a new Wistia video: folder placement, tagging,
metadata (title/description), and player customizations (captions/branding). Use this
skill whenever the user asks to upload a video to Wistia via Claude, or references a
video they just uploaded and wants it set up/organized/finished/published. Trigger on
phrases like "upload this to Wistia", "set up this video", "finish setting up the video
I just uploaded", "organize this video", "run the upload checklist", or when a Wistia
hashed_id/URL is shared right after an upload. Do NOT trigger for general Wistia
analytics, editing, or remix requests that aren't about onboarding a newly uploaded video.
---
# Wistia Video Upload Workflow
## Welcome message
Show this to the user before starting the workflow:
> Let's finish setting up your new video: folder, tags, title, description, captions, and branding.
>
> Have the video file or URL ready if it's not uploaded yet. Know which folder it belongs in? Even better, though not required.
>
> When we're done, it'll be fully organized and ready to publish.
---
Runs Wistia's standard new-video setup: folder placement, tagging, metadata, and player
customizations. Works whether the user is uploading a video through Claude right now or
pointing at a video they just uploaded some other way.
All Wistia actions use the Wistia MCP tools — call `tool_search` for the relevant tool name
(e.g. "wistia folders", "wistia tags", "wistia update media") before each new tool family,
since these are deferred tools and their exact parameters aren't in context by default.
---
## Step 0: Identify the video
- **New upload**: if the user is providing a file or URL to upload, hold off on calling
`upload-media-to-folder` / `import-media-from-url` until Step 1 (folder) is resolved —
folder is required at upload time for `upload-media-to-folder`.
- **Existing/recent upload**: if the user references a video without a hashed_id (e.g. "the
video I just uploaded"), use `get-medias` (sorted by most recent) to find the likely match
and confirm the title with the user before proceeding. If a hashed_id or Wistia URL is
given directly, use `get-medias` to pull its current details as a baseline.
Never guess a hashed_id. If there's any ambiguity about which video, ask.
---
## Step 1: Folder placement (always ask)
Folder is never inferred — always ask the user which folder/project this belongs in, every
time, even if a recent chat used the same folder.
1. Call `get-folders` to list current folders so you can offer real options rather than
guessing names.
2. Ask the user which folder to use (or whether to create a new one).
3. If new upload: pass the folder into `upload-media-to-folder` directly. If existing video:
use `move-media` to relocate it.
4. If the user wants a new folder, use `create-folder` first, then place the video there.
---
## Step 2: Metadata (title & description)
Write clean, sensible title and description copy from context — the video's existing title,
any source material discussed in the conversation, and the folder/purpose. There's no fixed
naming convention to follow, so use good judgment: clear, specific, human-readable.
- Apply directly via `update-media` (no need to ask for approval first).
- Always show the user what title/description was set as part of your final summary, so
they can ask for edits if it's off.
---
## Step 3: Tagging (confirm before applying)
Unlike metadata, tags are **not** applied without confirmation.
1. Call `get-tags` to pull the account's existing tag list.
2. Suggest tags for this video by matching against the existing taxonomy first — reuse
existing tags wherever they reasonably fit rather than inventing near-duplicates (e.g.
don't create "onboarding-video" if "Onboarding" already exists).
3. Only propose a brand-new tag if nothing in the existing list fits.
4. Present the proposed tag list to the user and wait for confirmation (or edits) before
applying anything.
5. Once confirmed: use `create-tags` for any genuinely new tags, then `bulk-tag-media` (or
the single-media tagging path) to apply the final set.
---
## Step 4: Player customizations (standard preset — apply directly)
The standard preset is: **use Wistia's account-level defaults, and make sure captions and
branding are turned on.** No per-video customization, no need to ask — just verify and fix
these two things:
1. **Captions**: check `get-captions` / `show-accessibility-customizations` for this media.
- If captions already exist and are enabled, leave as-is.
- If captions exist but are disabled, enable them via
`update-accessibility-customizations`.
- If no captions exist, note it in your summary and point the user to caption
generation in the Wistia app — don't generate captions from here.
- Note in your summary if caption generation is still processing (it's async — use
`get-background-job-status` if you need to check on it), since it won't be instant.
2. **Branding**: check `show-sharing-customizations` and/or `show-appearance-customizations`
for this media to see whether Wistia branding is currently shown or hidden.
- If branding is off, turn it back on (account default) via the corresponding
`update-*-customizations` call.
- If branding is already on (the default), leave everything else untouched — don't apply
any other appearance changes.
Do not touch thumbnail, colors, CTAs, or other appearance settings beyond this — those stay
at whatever the account default already is.
---
## Step 5: Summary
End with a short, concrete recap of what was actually done, e.g.:
```
✅ Video: [title] ([hashed_id])
📁 Folder: [folder name]
🏷️ Tags: [tag1, tag2, ...]
📝 Title: [title set]
📝 Description: [description set]
🎬 Captions: [on / processing / already present / missing — add in Wistia]
🎨 Branding: [on (default) / re-enabled]
```
If anything is still pending (e.g. captions processing), say so explicitly rather than
implying the checklist is fully done.
---
## Handling gaps and edge cases
- **Can't find the video** → ask for the hashed_id or Wistia share URL directly rather than
guessing from `get-medias`.
- **No folders exist yet** → offer to create one before proceeding; don't default to "no
folder" silently.
- **Ambiguous tag match** (e.g. both "Webinar" and "Webinars" exist) → surface the ambiguity
to the user in the confirmation step rather than picking one.
- **Caption generation still processing** → just note the pending status in the summary.
- **User only wants part of the checklist** (e.g. "just tag this one") → run only the
requested step(s), skip the rest, and don't ask about folder/customizations unless they
bring it up.
wistia-webinar-event-creation4.46 KB
---
name: wistia-webinar-event-creation
description: Create a Wistia webinar from a brief — gather the title, description, speakers, date/time, and registration goal, then create the event and save shared state for the rest of the workflow. This is STEP 1 of a three-skill webinar workflow (create → registration updates → post-event recap). Use whenever Sam wants to create, set up, schedule, or build a new webinar or live event. After the event is created it offers to set up registration reporting (step 2).
---
# Webinar Event Creation (Step 1 of 3)
Turn a rough idea ("we're doing a webinar on X") into a real Wistia webinar, and capture the details the later skills depend on. Half interview, half API call.
This is the first of three sequential skills:
1. **webinar-event-creation** ← you are here
2. **webinar-registration-updates** — scheduled pre-event pacing reports
3. **wistia-webinar-recap** — performance page after it airs
They coordinate through a shared **`webinar-state.json`** (see *Shared state*).
## What Wistia can and can't store
`create-webinar` (Wistia MCP) accepts: `title`, `description` (basic HTML), `scheduled_for` (ISO 8601 **UTC**, `...Z`), `time_zone` (IANA, e.g. `America/New_York`), `event_duration` (**minutes**, min 15), `folder_id` (optional).
Two things Sam cares about have **no native Wistia field**, so this skill owns them:
- **Speakers** — capture into `webinar-state.json` and fold into the `description` (a "Hosted by…" line). Be honest that Wistia has no speaker field.
- **Registration goal** — not a Wistia concept; store in `webinar-state.json` for steps 2–3.
**Two confirmed quirks:**
- **Timezone label is misleading.** Send `scheduled_for` as a true UTC instant; Wistia echoes it back as the *local wall-clock* time stamped `+00:00`. (Send `2026-08-21T17:00:00Z` for 1pm EDT → it returns `13:00:00+00:00`, i.e. 1pm ET.) When reading any `scheduled_for` later, interpret its clock time in the webinar's `time_zone`, not as UTC.
- **Auto-publishes.** New webinars come back `registration_status: published` — registration opens immediately at the `audience_link`. No separate publish step.
## Collect the brief
Check the conversation first; ask only for the gaps, **in one consolidated message**:
1. Title
2. Description / topic (offer to draft from the topic)
3. Speakers (names + roles)
4. Date & time + duration + time zone
5. Registration goal
For genuinely-unknown optionals (e.g. duration), suggest a default (60 min) rather than blocking.
## Steps
1. **Gather** the brief (one consolidated ask for missing items).
2. **Normalize the date** to a UTC `scheduled_for` + IANA `time_zone`. Double-check the conversion and state the local time back for confirmation — an off-by-timezone webinar is a real failure.
3. **Compose the description**, appending the speaker line.
4. **Create** with `create-webinar`; capture the returned `id` and `audience_link`.
5. **Write `webinar-state.json`** (see schema below) with `stage: "created"`.
6. **Confirm** back: title, local date/time, duration, speakers, goal, `audience_link`. Note registration is already open.
To edit an existing event instead of duplicating, use `update-webinar` (same fields + `id`).
## Shared state
All three skills read/write **`webinar-state.json`** in the user's working folder (the connected Cowork folder). It's the baton that hands the webinar from one skill to the next:
```json
{
"webinar_hashed_id": "32fei7lgyv",
"title": "...",
"scheduled_for_utc": "2026-08-21T17:00:00Z",
"time_zone": "America/New_York",
"event_duration_minutes": 45,
"speakers": [{"name": "Sam Balter", "role": "Host"}],
"goal": 500,
"audience_link": "https://home.wistia.com/live/events/32fei7lgyv",
"stage": "created",
"reporting_scheduled": false
}
```
`stage` advances through the workflow: `created` → `promoting` → `recapped`.
## Handoff → Step 2
After confirming the event, **offer the next step** rather than ending: ask Sam whether he wants to set up **registration reporting** now (the `webinar-registration-updates` skill). It will schedule recurring pacing updates anchored to the event date.
- If **yes** → invoke `webinar-registration-updates`. It reads `webinar-state.json`, so it already knows the webinar and goal.
- If **not now** → leave `reporting_scheduled: false`; he can start step 2 anytime.
Keep the offer short, e.g.: "Event's live and taking registrations. Want me to set up the registration tracker so you get scheduled pacing updates as it ramps toward Aug 21?"
wistia-webinar-recap6.99 KB
---
name: wistia-webinar-recap
description: Turn a finished Wistia webinar into a shareable recap — a short performance summary for the team, social clips with suggested copy, and a ready-to-publish blog post with the webinar embedded. Runs on the /wistia-webinar-recap command, or when Sam asks to "recap my last webinar", wants a webinar performance breakdown, show-rate/engagement numbers, or post-event assets. Step 3 of the webinar workflow (create → registration updates → recap), and is also handed off from the registration-updates skill once an event ends.
author: Wistia
version: 1.0.0
---
# Wistia Webinar Recap
The morning-after deliverable: how the webinar performed, packaged as a shareable performance page (short summary + charts), a grid of social clips with suggested copy, and a ready-to-publish recap blog post with the webinar embedded. Final skill in the sequence (`webinar-event-creation` → `webinar-registration-updates` → **this**), coordinating via **`webinar-state.json`**.
## When this skill starts
Open with this welcome message, verbatim:
> Once you finish a webinar in Wistia, run this skill to get a short performance summary to share with the team, social clips with suggested copy, and a ready-to-publish blog post with highlights and the webinar embedded inside.
>
> Make sure this skill is saved for later use, and then just type /wistia-webinar-recap when you want to run it again.
>
> So, what webinar would you like to start with?
Then wait for the user to name a webinar. If they already named one in the same message (a title, a Wistia link, or a hashed ID), skip straight to "Identify the webinar" and proceed. Don't dump the steps below into chat — just run them.
## Identify the webinar
Take whatever the user provides — a title, a `home.wistia.com/live/events/<id>` link, or a hashed ID — and resolve it with `get-webinars`. If they're vague ("my last one"), read `webinar-state.json` (handed off from step 2, usually `stage: "recapped_pending"`), or auto-detect the most recently **ended** real webinar via `get-webinars` (`lifecycle_status` `ended`/`vod_ready`, recent past, excluding test titles) and confirm. Pull the `goal` from state if present.
## Pull the data (Wistia MCP)
1. **`show-webinar-analytics`** — `registrations`, `attendance`, `engagement_rate`, `engaged_attendees`, `average_watch_time`, `total_watch_time`, `event_time`, `chats`, `chatted_attendees`, `qa_questions`, `poll_questions[]`.
2. **`show-webinar-histograms`** — `attendees_histogram` (retention curve), `chat_histogram`, `visual_focus_played_histogram`. Cheap, no pagination — prefer this for retention/watch-depth.
3. **`show-webinar-audience`** *(only if needed)* — per-registrant rows for channel tally or exact watch-depth. Token-heavy (embedded histograms); don't pull just for headline metrics.
`show-webinar-traffic-breakdown` is unreliable (frequent 500s) — derive channels from audience `referrer_domain`/`utm_source` instead.
## Compute the metrics
| Metric | How |
|---|---|
| Show rate | attendance / registrations (flag if < ~20%) |
| % to goal | registrations / goal |
| Avg watch depth | average_watch_time / event_time (>70% is excellent) |
| Retention to end | attendees_histogram[-1] / max(...) |
| Retention at 75% mark | attendees_histogram[int(0.75·len)] / max(...) |
| Active engagement | engaged_attendees / attendance; also engagement_rate |
| Chat participation | chatted_attendees / attendance; total chats |
| Satisfaction | rating poll in poll_questions[] (% 4–5★, weighted avg) |
On "% watched past 75%": the aggregate has no true per-attendee distribution cheaply — use **avg watch depth** + **retention at the 75% mark** as labeled proxies. Only paginate `show-webinar-audience` if Sam wants the exact distribution.
## Clips (Remix)
If clips for this event don't exist yet, offer to create them with Remix (Wistia's agentic editor): a mix of horizontal (16:9) and vertical (9:16) cuts across the strongest moments, each with a title, short description, and suggested social copy. Poll `get-remix` to completion, then confirm each export is a fully-rendered permanent media (`status: ready`, `progress: 1`) before embedding — instant previews expire and render blank. Save the permanent `hashed_id`s for the page.
## Output 1 — the performance page (primary)
A standalone **HTML performance page**. **Use the bundled template — don't rebuild it:** `assets/recap-page-template.html` (the polished Wistia-branded version; `assets/clip-picker-template.html` is the companion clip browser). Copy it to a new output file and swap in this webinar's data:
- **Nav + title & source:** `<title>`, `<h1>`, and the `.source` line (partner · live date · duration).
- **KPIs** (4): registrations, average engagement %, average watch time, average rating.
- **Charts** (bottom `<script>`): registrations-vs-average bar `data:[<regs>,<avg>]`; engagement gauge (doughnut) `data:[<eng>,<100-eng>]` with the `fillText('<eng>%')` center label; proportional star rating (`.stars-fg` width = rating/5); plus the chart-caption prose quoting those numbers.
- **Clips grid + blog embeds:** Wistia iframe `src` IDs and titles from the clips generated for this event. Blog body uses **horizontal 16:9 only** (brand rule); verticals live in the clip grid.
- **Recap blog post:** left-aligned, headline, byline date, body paragraphs (from the transcript), gated full-session video ID, CTA link, and the copy/download tools (Copy as Markdown / rich text / Download .md).
Keep the structure, Poppins/Inter fonts, and Wistia palette intact — it's already on-brand (https://wistia.github.io/brand/).
**Build as a standalone `.html`, NOT a Cowork artifact** — the artifact sandbox blocks Wistia embeds and Google Fonts, so players render blank. A standalone file opened in a browser loads everything.
## Output 2 — the short summary (closing message)
End with a tight summary linking to the page:
```
📋 Webinar Recap — <Title>
<turnout headline + the single strongest metric>
<3–4 metric lines: show rate · watch depth · engagement · rating>
Takeaway: <one honest sentence — what worked + the biggest lever next time>
📊 Full performance page → <link to the .html>
Get a full event performance breakdown plus post-event assets like social clips and a webinar recap blog post.
```
Keep the CTA line (or a close variant). Adapt metric lines to the data; drop any you don't have.
## Validated example (Pipeline webinar, pb4hqzr1dz)
469 regs (94% of 500), 81 attendees (17% show rate — low), 80% avg watch depth, 76% of peak retained at the 75% mark, 79% engagement, 94 chats / 24 chatters, 15 Q&A, 88% rated 4–5★ (avg 4.3). Verdict: small but deeply engaged audience; the lever next time is **attendance**, not content. `assets/recap-page-template.html` is this exact event, fully rendered.
## Wrap-up
After delivering, set `webinar-state.json` `stage: "recapped"`. This completes the workflow — note that to Sam. If the person hasn't saved the skill yet, remind them they can save it and re-run it anytime with **/wistia-webinar-recap**.
Referenced files: 2
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package author
- Wistia
Package observed Sep 30, 2026.
Technical details
- First seen
- Sep 30, 2026 · 22:02 UTC
- Last seen
- Oct 1, 2026 · 18:00 UTC
- Collection status
- Collected
plugin_asdk_app_69bbf84283c88191a617487b95f3a895
Download plugin data (JSON)