/screen-record — 스크립트 기반 브라우저 워크스루 비디오
사용 시기
사용자가 웹사이트의 동작을 보여주는 정교한 비디오를 원할 때 — 일반적으로 LinkedIn, Twitter(구 X), 영업 자료, 투자자 업데이트 또는 온보딩 문서용입니다. 사용자는 화면에 무엇이 표시되어야 하는지, 커서가 어떻게 움직여야 하는지, 나레이션이 대략 어떤 내용이어야 하는지 설명합니다. 당신이 스크립트를 작성하면 엔진이 커서 애니메이션, ElevenLabs TTS(음성 합성), 음악, 클릭 효과음 및 자막 생성을 처리합니다.
다음의 경우 이 스킬을 사용하지 마십시오: 이미 실행 중인 세션의 실제 화면 녹화(macOS 스크린샷 또는 QuickTime 사용), GIF(GIF 도구 사용), 실제 사람 목소리가 필요한 비디오(여전히 스크립트를 작성한 후 사용자가 직접 녹음하도록 할 수 있음).
작동 방식
엔진은 engine.mjs에 있습니다. runDemo(config)를 내보내며 다음을 수행합니다:
- 각 큐(cue)에 대해 ElevenLabs로 TTS 클립 하나를 생성합니다.
- ffmpeg로 부드러운 배경 음악과 UI 클릭 효과음을 생성합니다.
- 1920×1080 해상도의 헤드리스 Chromium을 실행하고 비디오를 녹화합니다.
- 사용자가 작성한
scenes(helpers)함수를 실행하여 브라우저를 제어합니다. - 오디오를 믹싱하고(나레이션 크게 + 음악 작게 + 클릭 미묘하게) ASS 스타일 자막을 삽입합니다.
- 최종
.mp4,.webm,.srt,.ass파일의 경로를 반환합니다.
워크플로우
1단계 — 사용자 요청 확인
다음을 구체적으로 파악하세요: 녹화할 URL, 흐름(커서가 해야 할 동작), 톤(격식/일상), 목표 길이. 빠진 것이 있다면 추측하지 말고 집중된 질문을 하나 하세요.
2단계 — API 키 확인
다음 순서로 ELEVENLABS_API_KEY를 찾으세요:
$CLAUDE_PLUGIN_ROOT/.env.local(또는 스킬 폴더 자체의.env.local)- 현재 프로젝트의
.env.local process.env
없다면 사용자에게 알리세요:
ElevenLabs API 키가 필요합니다. https://elevenlabs.io/app/settings/api-keys 에서 무료 키를 받으세요(월 10,000자, 약 15개 데모 제작 가능). 채팅에 붙여넣으면 제가 스킬의
.env.local에 저장하거나, 직접 추가하세요.
키가 있지만 엔진이 ApiKeyDeadError(HTTP 401/402/403)를 발생시키면 사용자에게 알리세요:
ElevenLabs 키가 거부되었습니다(취소, 만료 또는 할당량 초과). https://elevenlabs.io/app/settings/api-keys 에서 키를 교체한 후
.env.local을 업데이트하세요. 직접 하실 때까지 재시도하지 않겠습니다.
자동으로 재시도하지 말고, 사용자가 명시적으로 요청하지 않는 한 macOS say로 대체하지 마세요.
3단계 — 음성 선택
기본값은 Liam(TX3LPaxmHKxFdv7VOQHJ) — 활기찬 미국 남성, 현대적인 스타일. George(영국)와 Sarah(미국 여성)보다 가벼움. 다른 무료 등급 음성은 engine.mjs의 FREE_TIER_VOICES에 있습니다. 사용자가 "X 음성 사용"이라고 하면 카탈로그에서 찾으세요. "깊은/무거운/가벼운/따뜻한/여성" 등으로 말하면 그에 맞게 선택하세요.
라이브러리 음성(Charlotte, Rachel, Adam 등)은 유료 ElevenLabs 구독이 필요합니다. 사용자가 요청했는데 무료 등급인 경우 HTTP 402가 발생합니다 — 가장 가까운 무료 등급 음성으로 대체하고 사용자에게 알리세요.
4단계 — 대상 페이지 분석(필요한 경우에만)
페이지 구조를 모르는 경우 Playwright 또는 curl을 사용해 DOM을 검사하고 선택자를 찾으세요. 추측하지 마세요 — 선택자가 중요하며, 모호한 일치는 녹화 시간을 초과시킵니다.
동적 콘텐츠가 있는 SPA의 경우 클래스 이름보다 aria-label, role 또는 텍스트 선택자를 선호하세요. page.locator(s).first() 의미를 사용하세요(엔진의 헬퍼가 이미 이렇게 함).
5단계 — 장면 파일 작성
엔진을 가져와 runDemo를 호출하는 파일을 /tmp/screen-record-<name>.mjs에 생성하세요(사용자 저장소를 오염시키지 않음). 템플릿:
import { runDemo } from '/Users/<you>/Desktop/Projects/skill/screen-record-skills/engine.mjs';
// 또는 심볼릭 링크로 설치한 경우:
// import { runDemo } from '/Users/<you>/.claude/skills/screen-record/engine.mjs';
await runDemo({
baseUrl: 'http://localhost:3000',
outputDir: './output/onboarding-demo',
outputName: 'onboarding',
splash: {
label: 'Visit us at',
url: 'www.yourapp.com',
footer: 'YourApp — onboarding flow',
},
cues: [
{ id: 'intro', text: 'Welcome to YourApp. Let me show you around.' },
{ id: 'signup', text: 'Tap Sign Up to create an account.' },
{ id: 'form', text: 'Enter your email and a password.' },
{ id: 'done', text: 'And that is it — you are in.' },
],
// 선택 사항: 데모를 결정론적으로 만들기 위해 백엔드 모킹
routes: [
{ match: /\/api\/signup$/, fulfill: { status: 200, body: { ok: true } } },
],
// 선택 사항: 인증된 사용자를 미리 설정하여 데모가 로그인을 건너뛰도록 함
// initialAuth: { id: 'u1', email: '[email protected]', name: 'Demo User' },
scenes: async ({ page, baseUrl, sleep, speak, awaitCue,
clickAt, typeInto, moveCursorTo, hideSplash, typeSplashUrl,
configSwitch, showSwitch, hideSwitch, setAuth }) => {
// 장면 0: URL 스플래시(엔진이 이미 오프 카메라에서 baseUrl로 이동함)
speak('intro');
await typeSplashUrl('www.yourapp.com');
await awaitCue('intro');
await hideSplash();
await sleep(750); // 스플래시 페이드 시간
// 장면 1: 가입 버튼 클릭
speak('signup');
await clickAt('button:has-text("Sign Up")');
await awaitCue('signup');
// 장면 2: 양식 작성
speak('form');
await typeInto('input[type="email"]', '[email protected]');
await typeInto('input[type="password"]', 'CorrectHorseBattery');
await awaitCue('form');
// 장면 3: 제출
speak('done');
await clickAt('button[type="submit"]');
await sleep(1000);
await awaitCue('done');
},
});
6단계 — 실행
cd /Users/<you>/Desktop/Projects/skill/screen-record-skills
# 처음 한 번만:
npm install --silent
# 매번:
node /tmp/screen-record-<name>.mjs
각 실행은 최종 비디오 길이에 약 10초의 오버헤드를 더한 시간이 소요됩니다.
7단계 — 결과 보고
사용자에게 알리세요:
- 최종
.mp4파일 경로(LinkedIn 준비 완료, 1920×1080, H.264 + AAC) .srt파일 경로(LinkedIn이 자동으로 폐쇄 자막으로 가져옴)- macOS에서
afplay <path>로 재생해 볼 것을 제안
헬퍼 참조(장면에서 호출 가능)
| 헬퍼 | 기능 |
|---|---|
page | 필요한 경우 원시 Playwright Page 객체 |
baseUrl | 설정된 기본 URL |
sleep(ms) | await sleep(800) |
moveCursorTo(x, y, durationMs?) | 지정된 좌표로 부드럽게 이동 |
moveToSelector(selector, durationMs?, offset?) | 요소 중앙으로 부드럽게 이동 |
clickAt(selector, durationMs?) | 이동 + 펄스 + 클릭(효과음용 시간 기록) |
typeInto(selector, text, perChar?) | 클릭 + 지우기 + 문자별로 입력 |
recordClick() | 수동으로 클릭 시간 기록(효과음용) |
speak(cueId) | 큐 시작 표시. 종료는 시작 + 오디오 지속 시간 + 0.35초 |
awaitCue(cueId) | 큐의 나레이션이 끝날 때까지 대기 |
typeSplashUrl(text, perChar?) | 스플래시 URL 표시줄에 애니메이션 입력 |
hideSplash() | 스플래시 페이드 아웃(이후 항상 ~750ms 대기) |
configSwitch({ fromInitial, fromLabel, toInitial, toLabel, title, sub }) | 계정 전환 카드 내용 설정 |
showSwitch() / hideSwitch() | 계정 전환 오버레이 토글 |
setAuth(user) | localStorage[authStorageKey]를 새 사용자로 설정(계정 교체 데모용 showSwitch와 함께 사용) |
페이싱 규칙
- 큐당 한 문장. 약 12단어 이내로 유지하세요.
- UI 동작의 경우 작업 전에
speak(cue)를 호출하여 커서가 움직이는 동안 나레이션이 재생되도록 하세요. - 타이핑이 많은 장면의 경우 타이핑 시간이 나레이션보다 길어집니다 — 페이싱을 위해
awaitCue대신sleep을 사용하세요. - 전체 비디오 길이는 일반적으로
sum(오디오 지속 시간) + sum(sleep 시간)입니다. LinkedIn의 경우 30~75초를 목표로 하세요.
깨지기 쉬운 일반 선택자
- 단독
text=Foo는 여러 요소와 일치할 수 있습니다. 엔진이 이미 하는 것처럼page.locator(...).first()를 사용하거나,button:has-text("Foo")를 사용하여 태그로 범위를 좁히세요. - Tailwind 클래스 이름은 깨지기 쉽습니다.
aria-label, role, 의미 태그 + 텍스트를 선호하세요. - 모달/대화상자: 내부를 클릭하기 전에
[role="dialog"]또는 모달 제목 텍스트를 기다리세요.
하지 말아야 할 것
- 실제 나레이션 텍스트를 장면 파일에 직접 쓰지 마세요 — 자막과 오디오 동기화를 위해 나레이션은
cues[].text에 유지하세요. - 페이지 CSS에 커서를 하드코딩하지 마세요 — 엔진이 주입합니다.
- 장면 중간에
await page.screenshot()을 호출하지 마세요 — 커서가 이상한 위치에 잡힐 수 있습니다. 필요하면 사후에extractFrames를 사용하세요. .env.local을 커밋하지 마세요(.gitignore에 있음).