---
name: sales-demo-moment-library
description: "Build a module by module clip library from recorded sales demos with Vivu: find where reps stay on each product page and explain it, check each moment, write a Notion page. Use for rep onboarding."
---Demo moment library from recorded sales demos
This skill takes a handful of recorded product demos (meeting recordings with a screen share, exported as mp4) and the list of product modules a sales enablement lead wants new reps to learn, and returns a demo moment library: for each module, the recordings and minutes where a rep has that module's page up on the shared screen and stops to explain what problem it solves, with the page title read from a frame, the rep's words from the transcript, a link into the team's own recording platform at that minute, and an empty column for the enablement lead to mark what is good for training. Claude shortlists the demos from CRM stages and meeting titles, cuts each recording down to the demo part, prices the indexing against the user's Vivu plan, indexes the cut demos in a private Vivu project, runs one search per module, checks every hit against frames and the transcript, and writes demo_library.csv. With the user's approval it then creates the library as a Notion page.
The value is in what only the screen share shows. Reps say "let me stop here" or "this part matters for your security review" and rarely say the module's name, so which page they were explaining exists only on the screen, not in the transcript. The transcript has the opposite blind spot: a rep who says "we also have alert rules, but I'll skip that" names a module that was never opened. The screen alone is not enough either, because reps click past pages on the way to others. So Vivu finds where each module's page stays on screen, a sheet of frames shows how long it stayed, and the transcript shows whether the rep explained it; a moment reaches the library only when all three agree.
When to use
Use when a sales enablement lead, sales trainer or revenue ops person says "build a clip library of our demo moments by module for new reps", "where in our recorded demos do reps explain the permissions screen", "find the minute in each demo where the report builder gets walked through", or "turn these demo recordings into a training page by feature". For one question about one recording ("when did she open the integrations page?"), search Vivu directly. For finding what customers asked for in calls, use a feature ask evidence skill instead. This skill does not score reps, rank demos or pick a "best demo"; the enablement lead decides what is good for training.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Vivu's results are candidates. A moment becomes a library row only after the frames show the module's page staying on screen and the transcript shows the rep explaining it. Rejected candidates are counted and kept in rejected.csv with the reason.
- Stop and tell the user when a required capability or file is missing (no transcript, no ffmpeg, no write access in Vivu, no Notion connector). Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: the shortlist and the indexing cost before upload, the sample rows before the full library, and the Notion page before it is created.
- Find moments; never judge people. The library does not rate reps, and good_for_training is filled in by a person.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing.
| Requirement | Why | How to check |
|---|---|---|
| Vivu connector with write access | create a private project, open its upload page, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the connection is read only; the user reconnects Vivu and allows write access |
| A shell with ffmpeg and ffprobe on the machine that holds the recordings | cut the demo part, measure minutes, extract frames of the screen share | ffmpeg -version and ffprobe -version |
| The demo recordings as local mp4 files (downloaded from Zoom, Google Meet, Teams or a call recording tool) | frames and cuts come from the local files; Vivu has no export tool | ls DEMOS_FOLDER |
| A transcript per recording with timestamps (the meeting tool's VTT or SRT export, or the output of a local speech to text tool) | the explanation check and the quotes come from it, never from Vivu's reason text | ls DEMOS_FOLDER for one .vtt or .srt per recording; tell the user which have none |
| The link pattern of the team's recording platform, for example https://PLATFORM/RECORDING_ID?t=SECONDS | the library links to the team's own copy at the minute; Vivu result links expire | ask the user for one working example link |
| A browser that can open the Vivu upload page | the connector has no direct upload tool | the user opens the link, or Claude in Chrome is connected |
| Notion connector, only if the library should become a Notion page | Step 8 | search Notion for the parent page the user names |
The skill needs a shell and local files, so it runs in Claude Code on the user's computer (terminal or the Code tab of Claude Desktop). It downloads nothing from YouTube, so no residential IP is needed, and nothing is scheduled.
Inputs to collect
Ask for anything missing, most important first.
- The modules, four to six, each with a key, the page title as it appears in the product, and two or three controls visible on that page (for example report_builder: "Report builder", a list of data fields, a bar chart preview, an "Add filter" button). Required.
- The folder with the recordings (DEMOS_FOLDER). Required. If there are many, ask for the CRM stage and meeting title of each so Step 2 can shortlist.
- Which recordings to index. Default: Claude proposes four to six demo stage meetings whose titles mention a product demo.
- Where the demo part starts and ends in each recording. Default: Claude proposes it from the transcript (the first and last time the rep shares and talks about the product) and the user confirms.
- The recording platform link pattern. Default: no links; the library shows file name and time.
- Whether the library goes to Notion, and under which parent page. Default: no, demo_library.csv only.
- The Vivu project name. Default: "Demo library PRODUCT", private, where PRODUCT is the product name.
Files and state
Keep everything in one working folder next to the recordings:
demo-library/
modules.json module key, page title, visible controls, query
demos.csv shortlisted recordings: file, meeting title, CRM stage, demo start and end, minutes, transcript file
cut/ copies of each recording cut to the demo part (Step 3), the files that get uploaded
results/ raw search results, one JSON file per module and wording
frames/ sheets and full frames used for each check
demo_library.csv verified rows
rejected.csv candidates that failed a check, with the reason
state.json uploaded files and video ids, searches run with job ids, every hit with its verdict, rows written, Notion page created
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, searches already run are not rerun, checked hits keep their verdicts, and the Notion page is never created twice, so an interrupted run resumes where it stopped.
Step 1: Check the Vivu connector and the setup
Goal: every row of What you need is confirmed before anything is cut or uploaded.
- Call vivu_get_account. If the tool is missing, tell the user to add the Vivu connector in Claude (https://mcp.vivu.ai/mcp) and stop. If can_create_projects is not true, or a write call later fails with "has not granted vivu.write", ask the user to reconnect Vivu with write access and stop until they have.
- Run ffmpeg -version and ffprobe -version. List DEMOS_FOLDER and note which recordings have a transcript file.
- If Notion is wanted, search Notion for the parent page the user named.
- Create the working folders; ffmpeg does not create an output folder:
mkdir -p demo-library/cut demo-library/results demo-library/frames
Run every later command from inside demo-library/ (cd demo-library), so cut/, frames/ and results/ resolve to the folders just created.
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print versions, and the working folders exist.
Step 2: Confirm consent, list the modules and shortlist the demos
Goal: modules.json and demos.csv, with the user's confirmation that these recordings may be used.
- Ask Compliance items 1 and 2 before anything else in this step. Leave out any recording that fails them.
- Write one query per module from its page title and visible controls, using this shape:
| Field | Query template | Mode | maximum_results |
|---|---|---|---|
| MODULE_KEY | the presenter stays on the "PAGE_TITLE" page of the shared screen for a while (CONTROLS), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
PAGE_TITLE is the title exactly as the product shows it; CONTROLS are two or three things visible on that page, in plain words. Write what is on the page, not what the rep says: reps rarely name the module, and naming it in the query invites hits where the module is only mentioned. The "stays ... not clicked past" clause matters: without it, every brief pass through the page comes back too. All searches are precise because the library needs time ranges; a fast search only returns whole files, and the demos are already shortlisted. maximum_results 10 is more than the stays one module gets in four to six demos, and it is the ceiling on what comes back: a list that stops at exactly 10 means the cap was hit, so raise it and rerun that module. 3. Shortlist from text first. Read the meeting titles and CRM stages and pick four to six demo stage meetings. A whole call archive cannot be indexed on any plan, and discovery calls rarely share the product. Write demos.csv. 4. Show the modules, the queries and the shortlist, and wait for a yes.
In our test run the recordings were made for the test, so the consent question and shortlisting from CRM stages and meeting titles were not exercised in our test run; the modules and queries were written as above.
Done when the user has confirmed consent and approved modules.json and demos.csv.
Step 3: Cut each recording to the demo part
Goal: one file per recording that holds only the demo, in cut/, so index minutes are spent only on the part that can hold a module walkthrough.
- Find the demo part in the transcript: from the rep's first "let me share my screen" or equivalent to the last product talk before pricing or next steps. Show the proposed start and end per recording and let the user adjust.
- Cut without re-encoding (RECORDING_FILE is the path to the original recording in DEMOS_FOLDER; DEMO_START and DEMO_END are in seconds; CUT_FILE is the original file name without .mp4, so rows match back):
ffmpeg -v error -ss DEMO_START -to DEMO_END -i RECORDING_FILE -c copy cut/CUT_FILE.mp4
A stream copy starts at the nearest keyframe, so the cut can begin a few seconds early; that is fine for search. Keep DEMO_START in demos.csv: every time Vivu returns is relative to the cut file, and library times and links need DEMO_START added back to point into the full recording.
Step 3 was not exercised in our test run: the test recordings held only the demo, so nothing was cut and no offset was added.
Done when cut/ holds one file per shortlisted recording and demos.csv records each DEMO_START.
Step 4: Price the indexing and get approval
Goal: the user sees the cost before anything is uploaded.
- Measure each cut file and sum the minutes:
ffprobe -v error -show_entries format=duration -of csv=p=0 cut/CUT_FILE.mp4
- Call vivu_get_usage for the plan and what remains this month.
- Estimate search credits: one precise search per module, plus one rewording per module in case the first page is not clean. A precise search uses 5 credits in total. Label the total as an estimate.
- Show one table and wait for approval:
| This run | Remaining on the plan | |
|---|---|---|
| Index minutes | measured sum | from vivu_get_usage |
| Search credits | estimate | from vivu_get_usage |
For reference, the Free plan has 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. As an estimate, five demo parts of about 18 minutes fill half of Premium and do not fit Free, where one demo part is about the limit. If the shortlist does not fit, offer these levers in order: shorten the cuts to the product walkthrough only, drop demos whose titles or notes suggest they skip the modules, split the work across months, and only then a larger plan. Never drop a recording the user asked for without saying so.
In our test run the account was an admin account whose vivu_get_usage shows no remaining allowance, so the comparison against a real plan and the approval were not exercised in our test run.
Done when the user has approved the minutes and the credit estimate.
Step 5: Upload and index
Goal: every cut file is ready in a private Vivu project.
- Call vivu_list_projects and reuse the project named in the inputs if it exists. Otherwise call vivu_create_project with that name and visibility "private". Demo recordings show customer names and voices; the default visibility is the whole organization.
- Call vivu_open_upload_page with the project ID right before the upload. The link expires in 180 seconds and is a sign in link, so never paste it into a message or file. Give it to the user to open in their own browser and choose the files in cut/, or attach them with a browser tool that can upload local files. Claude in Chrome accepts at most 10 MB per upload call, and an 18 minute screen share is usually larger; the user adds those in the Vivu web app. Never split or recompress a recording to fit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to demos.csv by file name; Vivu turns spaces and punctuation in names into "_", so compare names with those characters replaced. Record the video IDs in state.json.
In our test run the files went up through a script, not through a user's browser or Claude in Chrome; that upload path was not exercised in our test run.
Done when vivu_list_videos shows every file in demos.csv as ready.
Step 6: Search each module and check every hit
Goal: for each module, a list of checked stays with rejected candidates counted.
Run each module's query with vivu_search_videos (project_id, query, mode "precise", maximum_results 10). It returns a job ID. Call vivu_get_search_results until complete is true; each status call can wait up to 45 seconds, so a pending search is not a stalled one. Save each completed result as results/MODULE_KEY.json. Show the result page link in the live reply only; it expires after four hours, so it never goes into the library, Notion or any file.
Vivu returns a time range that contains the page, not the exact seconds, and the reason text is a paraphrase. Finding a page by its title and controls has held up in tests; telling a real stay from a page clicked past is a weak capability, tested only on synthetic demos with clean pages, so the sheet in check 1 decides, never the search alone. Check every hit in this order:
- Did the page stay? Make a sheet of one frame every half second across the window (START is start_ms / 1000, DURATION is (end_ms - start_ms) / 1000; the 8x6 grid holds 24 seconds, so for a longer window raise the second number):
ffmpeg -v error -ss START -t DURATION -i cut/CUT_FILE.mp4 -vf "fps=2,scale=320:-2,tile=8x6" -frames:v 1 frames/MODULE_KEY_HIT_sheet.png
HIT is h plus the hit's number within the module (h1, h2, ...). Keep the quotes around the filter. Count the cells that show the module's page; each cell is half a second. Windows start and end a little outside the stay, so the first and last cells often show the neighboring pages. Reject the hit as PASS_THROUGH when the page is up for less than about 8 seconds (16 cells), and write the stay's first and last second from the sheet into state.json. 2. Did the rep explain it? Read the transcript lines between the stay's first and last second. The hit is real only if the rep says what the module is for or what problem it solves. Reject it as NO_EXPLANATION when nobody talks, or the talk is about something else ("sorry, wrong page", pricing, small talk). When a recording has no transcript, keep the hit as a candidate: read vivu_get_video_summary with include_segments, start_ms and end_ms around the stay as a second signal only, mark the quote NOT VERIFIED, and tell the user to listen to that stretch. 3. Read the page title from a full frame in the middle of the explanation (SECONDS), not from the reason text and not from the window's first frame, which usually still shows the previous page:
ffmpeg -v error -ss SECONDS -i cut/CUT_FILE.mp4 -frames:v 1 -q:v 3 frames/MODULE_KEY_HIT_read.png
If the title is not the module's, reject the hit. If the frame shows a customer's company name, a real account or real customer data, keep the row but set flag CUSTOMER_DATA_VISIBLE so it stays out of the training page. 4. Rejected hits stay in results/ and go into rejected.csv with the reason; they are counted, not dropped silently. 5. A module with fewer rows than the user expected does not prove no rep explained it. Rerun that module once with the page wording below, which describes only the page and so returns every appearance of it including brief passes, and put each new hit through checks 1 to 3. Tell the user how many stays the second pass added.
Check 5 was not exercised in our test run: the page wording ran before the stay wording on every module, and the one stay the stay wording missed was not added back to the library.
These wordings ran in our test, for five modules of one admin console; adapt the title and controls to the user's product and keep the shape:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| report_builder | the presenter stays on the "Report builder" page of the shared screen for a while (a list of data fields, a bar chart preview and an "Add filter" button), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
| roles_permissions | the presenter stays on the "Roles and permissions" page of the shared screen for a while (a grid of roles against permissions with checkboxes and a "Save changes" button), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
| integrations | the presenter stays on the "Integrations" page of the shared screen for a while (a grid of app cards, each with a "Connect" button), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
| alert_rules | the presenter stays on the "Alert rules" page of the shared screen for a while (a list of rules with on/off toggle switches and threshold values), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
| audit_log | the presenter stays on the "Audit log" page of the shared screen for a while (a table of timestamps, users and actions with an "Export log" button), not a page that is only clicked past for a second or two on the way to another page | precise | 10 |
| MODULE_KEY, second pass only | the "PAGE_TITLE" page open on the shared screen: CONTROLS | precise | 10 |
Why this chain: the module query finds where the page is on screen, the half second sheet turns the window into a measured stay and rejects pages that were clicked past, and the transcript flips from what was shown to what was said, so a stay without an explanation never becomes a row. There is no speech search, because reps explain modules without naming them.
Worked example from our test run
In our test run we used 4 synthetic demo recordings, 5.14 minutes in total, made for the test: one admin console with a home page and five module pages that share the same layout, navigation and colors. Three recordings had a synthetic rep voice; in each, the rep stayed on three module pages and explained what they solve without ever saying the module's name, clicked past two other module pages, and named one more module while the home page was on screen. The fourth recording was a silent control with the same kind of stays and passes and no voice at all. Before searching we wrote down every stay, pass and mention: 12 stays in total. From the upload to all 4 recordings ready took 2.1 minutes.
The first wording, with only the page title and visible controls, found every stay but could not tell a stay from a pass: across the five modules it returned 20 moments, 12 real and 8 false, and all 8 false ones were pages clicked past. After 1 rewording on the same corpus (the wording in the table above), the five queries returned 11 moments: 11 real, 0 false and 1 missed. The miss was a full stay on the Integrations page that the first wording had returned. The integrations query returned 2 moments, 2 real, with 1 missed; the report_builder query 3 moments, 3 real; the roles_permissions, alert_rules and audit_log queries 2 moments each, all real. None of the mentions made on the home page came back in either wording. The median window per module was 21 to 22 seconds, a little wider than the stay on both sides, so the sheet, not the window, gives the start and end.
In the three narrated recordings the rep's words also marked stays and passes (a pause line on some stays, an apology on some passes), so speech could have carried the signal there. On the silent recording, where nothing is said, the stay wording still returned all 3 stays and none of the 2 pages clicked past, so telling a stay from a pass came from the screen, not from the narration. The half second sheets showed every stay clearly, and the full frame read the right title every time. The transcript check rejected every hit on the silent recording as NO_EXPLANATION, so the library kept the stays found in the three narrated recordings. The transcripts were the fixture's script with its measured timing, not a meeting tool export or speech to text output; real demos with cursor movement, scrolling, pop ups, a rep talking over a page they are leaving, customer data on screen, and pages from similar products were not exercised in our test run.
Done when every hit of every module has a verdict in state.json and the user has seen the real and rejected counts per module.
Step 7: Assemble demo_library.csv and confirm the sample
Goal: one table the enablement lead can sort by module and turn into training.
- Build one row per verified stay. Use the stay's first and last second from the sheet, plus DEMO_START from demos.csv, for the times in the full recording:
module,recording_file,start_mmss,end_mmss,page_title_read_from_frame,frame_path,explanation_quote,quote_source,internal_link,good_for_training,flag,status
Report builder,RECORDING_FILE,00:08,00:26,Report builder,frames/report_builder_h1_read.png,"Here they pick exactly what they want to see, and they get the answer themselves in about a minute.",meeting transcript,https://PLATFORM/RECORDING_ID?t=8,,,verified
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| module | the module name from modules.json | none |
| recording_file | the original file name from demos.csv | none |
| start_mmss, end_mmss | first and last cell of the stay on the sheet, plus DEMO_START | the Vivu window, marked UNCERTAIN |
| page_title_read_from_frame | read from the full frame | UNCERTAIN |
| frame_path | the frame the title was read from | none |
| explanation_quote | the transcript lines inside the stay, word for word, shortened to one or two sentences | NOT VERIFIED |
| quote_source | meeting transcript or local speech to text | none |
| internal_link | the user's link pattern with SECONDS = start in the full recording | blank |
| good_for_training | left empty for the enablement lead | blank |
| flag | CUSTOMER_DATA_VISIBLE when a frame shows customer data | blank |
| status | verified | candidate when the transcript is missing |
- Show two or three sample rows and the mapping and wait for a yes before writing the full table. Say which parts are inferred: whether a sentence "explains the module" is Claude's reading of the transcript; if it is wrong, a row may show a rep who only described the screen, which the enablement lead catches when filling good_for_training.
- Write demo_library.csv and rejected.csv, and give the user the counts: rows per module and per recording, candidates rejected per module and why. When a module has no row, say so plainly; do not fill it with a pass or a mention.
In our test run the library and rejected.csv were built from the checked hits; the user's approval of the sample rows was not exercised in our test run.
Done when demo_library.csv exists with one row per verified stay and the user has approved the sample rows.
Step 8: Create the Notion page (optional)
Goal: the library sits in the team's wiki as one page, grouped by module.
- Only if the user asked for it in the inputs. Render the page: a short intro, then one heading per module with a table of recording, start, end, what the rep says, internal link and an empty good for training column. Leave out rows flagged CUSTOMER_DATA_VISIBLE. Use the recording platform link or the file name and time, never a Vivu result page link (it expires after four hours).
- Creating a Notion page acts as the user. Name the parent page, show the full rendered page, and wait for an explicit yes. Record the created page in state.json so a rerun never creates it twice.
Step 8 was not exercised in our test run: the page was rendered to a file and the Notion connector was never called.
Done when the approved page exists under the named parent and is recorded in state.json, or the user chose demo_library.csv only.
Compliance
- Use only calls recorded under the company's recording policy, where the customer was told the call is recorded and internal training is an allowed use. Ask before Step 2 and leave out any recording that does not meet this.
- Recordings of customers are personal data. Ask before Step 5 whether the company allows uploading them to a third party service, and create the Vivu project as private. The recordings stay in the user's Vivu project until the user deletes them; the skill never calls vivu_delete_video or vivu_delete_project unless the user asks, and confirms first.
- Frames that show a customer's name, account or data are flagged CUSTOMER_DATA_VISIBLE and kept out of the training page.
- The library finds moments. It never scores reps, ranks demos or names a "best demo"; good_for_training is a person's call.
- No face, voice or identity recognition. Vivu only finds when a page is on screen; it does not cut or edit video, and any clip is cut by the user from their own files.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) pages the rep only clicked past come back as hits; the first wording in our test run returned 8 false of 20, every one a pass through a page | a query that describes only the page finds every appearance of it, however brief | use the stay wording from Step 2, and reject any hit whose sheet shows the page only briefly (the Step 6 threshold) |
| (observed) a stay the first wording found is missing after the rewording; the integrations query in our test run returned 2 real and 1 missed | the stricter wording also drops some real stays | when a module has fewer rows than expected, rerun the page wording for that module and check each new hit the same way |
| (observed) a page stays on screen but nobody explains it; every hit on the silent test recording was a real stay with no words in the window | the rep is silent, or talking about something else, while a page is up | the transcript check rejects it as NO_EXPLANATION |
| (observed) the video summary says a module was shown when it was only named: "Briefly navigates through alert rules and displays the system audit log tracking user actions." | summary segments mix what was said with what was on screen | use segments only as a second signal; the sheet decides what was on screen |
| (observed) the window's first frame shows the previous page | windows start and end a little outside the stay | read the title from a full frame in the middle of the explanation, and take start and end from the sheet |
| a rep names a module without opening it and a transcript search would count it | the transcript holds the words, not the screen | this skill searches the screen; never add a row from the transcript alone |
| the reason text describes controls or data that are not on the page | the reason is a paraphrase, not a reading of the frame | read the title and data from the full frame only |
| a module comes back empty | an empty result does not prove no rep explained it; the page may look different from the query | check the page title and controls against a frame of that page, reword once, run the page wording, and tell the user |
| a list stops at exactly maximum_results | the cap cut the list short | raise maximum_results and rerun that module |
| library times point a few minutes early in the full recording | times were taken from the cut file without DEMO_START | add DEMO_START from demos.csv to every time and link |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |
| upload page asks to sign in or shows an error | the one time upload link expires after 180 seconds | request a new link right before opening it |
| a file is rejected by the browser upload tool | Claude in Chrome takes at most 10 MB per upload call | the user adds that file in the Vivu web app; never split or recompress it |
| a result's file name does not match demos.csv | Vivu replaced spaces or punctuation in the name | compare names with those characters replaced by "_" |