Communitygithub.com

delacournz/demo-video

실제 시뮬레이터에서 구동되는 warden(또는 예제 앱)의 짧은 제품 데모 비디오를 만들고, 전화기 옆에 타이핑 터미널을 배치합니다. 데모 비디오, 문서나 X용 기능 클립, UI 변경 후 재녹화, 또는 `*.demo.ts` 정의 추가/편집 시 사용하세요. 정의 형식, `bun run demo`, 녹화 -> 콘택트 시트 -> 렌더링 루프, 정직성 규칙, X 내보내기 사양 및 문서 임베딩을 다룹니다.

demo-video란 무엇인가요?

`demo-video`는 iOS 시뮬레이터에서 실제로 구동되는 warden(또는 예제 앱)의 제품 데모 비디오를 제작하는 AI 에이전트 스킬입니다. 1920x1080 해상도의 비디오로, 왼쪽에는 제목과 실제 warden 명령어를 타이핑하는 터미널, 오른쪽에는 둥근 모서리와 3px 테두리의 전화기 화면이 배치됩니다. 이 스킬은 화면에 시각적 효과가 있고 몇 초 내에 완료되는 기능(예: 생체 인식, 알림, 제스처)에 최적화되어 있으며, 각 클립은 10~25초, 3~6개의 자막 단계로 구성됩니다. 핵심 워크플로는 `scripts/demo/` 디렉토리의 도구 키트(`demo.ts` CLI, `record.ts`, `render.py`, `timeline.ts`)를 기반으로 하며, `bun run demo <feature>/<clip>` 명령어로 실행됩니다. 먼저 임대된 iOS 시뮬레이터에서 비디오를 녹화하고, 각 단계의 시작과 끝을 비디오 클록과 동기화하여 명령어 출력이 정확한 시점에 표시되도록 합니다. 녹화 후에는 콘택트 시트(sheet.jpg)를 검토하여 각 출력 라인이 전화기 화면의 반응과 일치하는지 확인하고, 마지막 프레임이 종료 상태를 보여주는지 검증합니다. 정직성 규칙이 엄격하게 적용됩니다: 모든 출력된 명령어는 실제로 실행되었으며, 출력 텍스트는 명령어의 출력이 아닌 전화기 화면의 실제 반응을 나타내야 합니다. 화면에 보이지 않는 효과는 캡션을 숨기고, 비밀 정보는 `maskSecrets`로 자동 마스킹됩니다. 실제 시뮬레이터와 실제 iOS UI만 사용하며, 가짜 프레임을 합성하지 않습니다. 결과물은 H.264 High, 30fps, 2MB 이하의 파일로 `apps/docs/public/demos/<feature>/`에 mp4와 jpg 형태로 저장되며, MDX `<DemoClip>` 컴포넌트로 문서에 임베드됩니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/delacournz/warden/tree/HEAD/.claude/skills/demo-video

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

demo-video은(는) 무엇을 하나요?

bun run demo <feature>/<clip> 명령어는 임대된 iOS 시뮬레이터에서 테이크를 녹화한 후 1920x1080 비디오를 렌더링합니다. 왼쪽에는 제목, 각 실제 warden 명령어를 타이핑하고 단계가 끝날 때 출력을 표시하는 터미널, 오른쪽에는 둥근 모서리와 3px 테두리가 있는 전화기 화면이 배치됩니다. 키트: scripts/demo/ (demo.ts CLI, record.ts, render.py, timeline.ts, demo.types.ts). 정의: apps/example/demos/<feature>/<clip>.demo.ts (name은 반드시 <feature>/<clip>와 같아야 함); 자산: apps/docs/public/demos/<feature>/<clip>.mp4|jpg. 기능별로 하나의 폴더 (biometrics, network, location, clock), 소문자, 하이픈 사용. 내부: 체인지셋 없음.

1. 제작 시기

  • 화면에 시각적 효과가 있고 몇 초 내에 완료되는 기능(생체 인식, 알림, 제스처). 로그, 성능 숫자 또는 표로 더 잘 보여주는 것은 해당하지 않음.
  • 앱 UI 또는 명령어 동작이 변경된 후: mp4를 편집하지 말고 다시 녹화.
  • 클립당 하나의 이야기, 10-25초, 3-6개의 자막 단계.

ffmpeg, ffprobe, uv (brew install ffmpeg uv), Xcode 및 node_modules/@expo-google-fonts의 폰트 (bun install)가 필요합니다.

2. 정의 작성

demo: DemoDefinition 내보내기 (scripts/demo/demo.types.ts). apps/example/demos/biometrics/faceid.demo.ts를 복사하세요.

필드의미
title / subtitle / terminalTitle / footer정적 텍스트; title은 주장, subtitle은 한 줄
phoneCaption전화기 아래 줄, 기본값 real simulator · not an animation; ""로 숨김
claim{ biometrics?, profile? } -> warden claim ios --json --label demo-<feature>-<clip> --ttl 30m [--biometrics]
appbundleId, kind (simulator 기본값), project (저장소 기준 cwd), deepLink (--settle과 함께 열림), appearance (light/dark, 임대된 udid에서 simctl ui로 설정, 이후 복원)
setup앱이 열린 후, 딥 링크 전에 카메라 밖에서 실행되는 warden argv; ["ui","network","set","online","--relaunch"]는 iOS dylib 실행을 위해 네트워크/시계 단계에 필요. 항목은 argv 또는 { argv, optional: true }일 수 있음: 선택적 단계가 0이 아닌 종료 코드로 종료되면 로그에 기록됨 (optional setup warden … -> <code>) 테이크가 실패하는 대신 (표시되지 않을 수 있는 권한 프롬프트)
ready딥 링크 후 비디오 시작 전에 통과해야 하는 warden ui wait 선택자 (예: id=bio-screen); 단계는 마운트되지 않은 화면을 누르지 않음
leadInMs / tailHoldMs단계 1 전 / 마지막 프레임 유지 시간, 렌더러에 의해 패딩됨 (테이크 꼬리는 1초)
steps[]argv (warden 이후; 레코더가 --udid를 추가함), output (ok는 벡터 체크 표시, plain은 음소거, lines는 여러 음소거 줄 출력), pauseAfterMs, shown?, hidden?, quote?
outro마지막 출력 후 입력되는 # comment 줄

output: { kind: "lines", text: [...] }는 항목당 하나의 음소거 줄을 출력합니다 (약 6개 유지; 첫 번째 이후 각 항목은 레이아웃에서 항목 피치의 0.4를 차지) 파일에서 표시되는 코드나 텍스트용. quote: { file }과 함께 사용 (경로는 app.project 기준): 단계 후 레코더는 출력된 모든 줄(trim 됨)이 해당 파일에 있는지 확인하고, 그렇지 않으면 quoted line not in <file>: <line> 오류를 throw하므로 인용된 줄이 조작되지 않습니다. 줄은 여전히 maskSecrets를 거칩니다.

시간은 수동으로 작성되지 않습니다: 레코더는 각 단계의 시작과 끝을 비디오 클록에 대해 측정하고 출력 라인은 단계의 END에 나타납니다.

3. 루프

bun run demo <feature>/<clip> --record-only      # claim, 9:41 상태 표시줄, 실행, 녹화, id로 해제
bun run demo <feature>/<clip> --render-only --sheet   # mp4 + jpg 빌드, <work>/sheet.jpg 작성
bun run demo <feature>/<clip>          # 둘 다

작업 디렉토리: $TMPDIR/warden-demo/<feature>/<clip> (--out dir): take.mp4, steps.json, plan.json, sheet.jpg; 테이크 실패 시 failure-snapshot.txt (오류 + ui snapshot -i). 여기 있는 어떤 것도 커밋되지 않습니다.

  1. 녹화.
  2. sheet.jpg 읽기 (4x4 타일, 초당 1개, pts가 표시됨). 로그와 비교: 각 출력 라인이 전화기가 시각적으로 반응할 때 나타남; 관련 없는 알림, 키보드, 빨간 상자 또는 잘못된 상태 표시줄을 보여주는 프레임 없음; 마지막 프레임은 종료 상태를 보여줌.
  3. 자세히 보려면: ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.png 후 Read.
  4. 정의 수정 (pauseAfterMs, 단계 드롭/숨기기, shown) 후 --render-only; 테이크 자체가 잘못된 경우에만 다시 녹화. 배치 수정: 라운드당 한 번 녹화.
  5. apps/docs/public/demos/<feature>/에 mp4 + jpg 커밋.

4. 정직성 규칙

  • 출력된 모든 명령어는 실제 앱에 대해 실행되었습니다. argv와 의미가 다른 shown은 없습니다 (단지 --timeout과 같은 플래그만 자릅니다).
  • 출력 텍스트는 명령어가 출력한 내용이 아니라 전화기가 수행한 작업입니다. 누름(press)에 시각적 효과가 없으면 (시트 진입 애니메이션 중 드롭, 변경 없음) 캡션을 추가하지 마세요: hidden: true, 또는 더 긴 이전 pauseAfterMs로 타이밍을 수정하세요.
  • 첫 실행 권한 알림은 새로 설치할 때만 표시됩니다. claim은 웜 빌드를 재사용하므로 테이크에 표시되지 않는 한 알림에 캡션을 추가하지 마세요.
  • 앱 측 우회 또는 모의 프롬프트 없음: 제목의 claim은 테이크에서 true여야 합니다.
  • 비밀 정보: maskSecrets는 key=value, ghp_/sk- 스타일 토큰 및 긴 hex/base64를 출력 라인에서 숨깁니다. 그래도 전화기에서 토큰, 이메일, 장치 이름을 확인하세요.
  • 실제 시뮬레이터, 실제 iOS UI. 가짜 프레임을 합성하지 마세요.

5. 주의 사항 (키트에 인코딩됨)

  • simctl 비디오는 VFR이며 유휴 꼬리를 드롭합니다: 렌더러는 fps를 정규화하고 마지막 프레임을 계획 길이까지 패딩합니다 (tpad).
  • 비디오 시간은 명령어 로그와 약 0.5초 이내로 일치합니다; 레코더는 video start 직후에 클록을 시작합니다.
  • Martian Mono에는 체크 글리프가 없습니다: render.py는 이를 벡터로 그립니다.
  • 프레임은 문서 Device Hub 어두운 팔레트 (Mona Sans + Martian Mono)입니다; app.appearance: "dark"로 설정하여 전화기가 일치하도록 합니다. 레코더는 이후 시뮬레이터의 모양을 복원합니다.
  • 터미널 오버플로우: computeTerminalLayout는 피치와 폰트를 17px로 줄인 다음 가장 오래된 항목을 스크롤하여 없앱니다.
  • 장치 비디오 제한 3분; 그래도 테이크는 40초 미만으로 유지하세요.
  • 상태 표시줄: xcrun simctl status_bar <udid> override는 임대된 udid에서 허용됩니다; 레코더는 이후 이를 지웁니다.

6. X 내보내기

1920x1080, H.264 High, yuv420p, 30 fps, +faststart, 오디오 없음, <= 2:20, 작은 파일 (키트는 CRF 사다리 24..36을 통해 <= 2MB 목표). 확인:

ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<feature>/<clip>.mp4

7. 자산 및 문서 임베드

출력: apps/docs/public/demos/<feature>/<clip>.mp4 + <clip>.jpg (포스터 = 마지막 프레임), /demos/<feature>/<clip>.mp4에서 제공됨 (Vite public/). DemoClip MDX 컴포넌트로 임베드 (apps/docs/src/components/demo-clip.tsx): 자동 재생 및 음소거, 반복, 축소된 모션에서 포스터 위에 멈춤. src에는 확장자가 없음; label은 클립이 보여주는 내용을 설명합니다 (캡션과 일치). 원시 <video>는 apps/docs/src/lib/source.test.ts를 실패시킵니다.

<DemoClip src="/demos/<feature>/<clip>" label="warden ui … doing … on a leased simulator" />

명령어를 명명하는 캡션 문장을 추가하세요. 배치 릴 apps/docs/public/demos/e2e/batch.* (<DemoReel>로 임베드됨)는 다른 파이프라인입니다 (bun run demo:compose).

8. warden dev 릴 (dev/hero)

세 번째 파이프라인: warden의 실제 개발 대시보드 (왼쪽, 터미널 카드 안) 옆에 시뮬레이터 warden dev가 자체적으로 claim함 (오른쪽). 아무것도 입력되거나 가짜가 아닙니다: dev-take.ts는 PTY에서 warden dev ios --label demo-dev-<clip> --headless --no-shutdown을 생성하고 (Bun.spawn({ terminal })) @xterm/headless로 미러링하고 (대기를 위해 프레임 읽기) 모든 PTY 청크를 tui.cast에 씁니다; 클록 원점은 생성 시점입니다. 레이블이 지정된 장치 임대를 위해 warden ls --json을 폴링한 다음 어두운 + 9:41로 설정하고, 앱을 제거하고 ui screen video를 시작합니다 (전화기는 그때까지 검은색). 개발 자식은 자체 WARDEN_SESSION_ID를 얻습니다: warden dev는 호출자 세션이 보유한 장치 임대를 채택하고 모든 팀원 에이전트가 하나를 공유하므로, 이것이 없으면 테이크가 다른 사람의 시뮬레이터를 구동할 수 있습니다.

bun run demo:dev dev/hero --record-only        # claim, 앱 제거, 9:41 + 어두움, 녹화, id로 해제
bun run demo:dev dev/hero --render-only --sheet # agg + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg}, <work>/sheet.jpg
bun run demo:dev dev/hero                      # 둘 다
  • 키트: dev-demo.ts (CLI), dev-take.ts (레코더, runBeats), dev-render.ts (agg + compose, DEV_LAYOUT), dev-frame.py (카드, 테두리, 마스크), dev-timeline.ts (cast 텍스트, 램프 세그먼트, 캡션). 타입: DevDemoDefinition / DevBeat in demo.types.ts. 정의: apps/example/demos/dev/<clip>.demo.ts; 작업 디렉토리 $TMPDIR/warden-demo/dev/<clip> (take.mp4, tui.cast, dev.json, sheet.jpg, failure-frame.txt).
  • 비트(beat) = waitScreen (TUI 프레임에 대한 정규식) + waitPhone (ui wait 선택자) -> 액션 (TUI에 keys, 및/또는 저장소 파일의 실제 edit) -> afterPhone 선택자 (핫 리로드 랜딩) -> pauseAfterMs. dev.json은 비디오 클록에서 각 비트의 시작/준비/종료를 보유합니다; 캡션은 비트의 액션부터 다음 액션까지 실행됩니다.
  • 편집은 실제 소스 편집입니다 (welcome.tsx 본문 문자열), finally에서 바이트 단위로 되돌림; 테이크 후 git diff apps/example/src가 비어 있는지 확인하세요.
  • uninstallFirst는 임대된 시뮬레이터에서 앱을 제거하여 warden dev가 캐시에서 개발 빌드를 카메라에 설치하도록 합니다. 지문의 첫 번째 테이크는 로컬 빌드입니다 (분 단위, dev build 행 컴파일): 캐시를 워밍업하기 위해 한 번 녹화한 다음 다시 녹화하세요. warden app ensure --kind dev는 warden dev가 읽는 캐시를 워밍업하지 않습니다.
  • 속도 램프: DevBeat.ramp: N은 해당 비트의 대기를 N배로 가속합니다 (실제 속도로 1.2초 헤드와 1초 테일 유지) 그리고 릴은 × N speed 태그를 그립니다. 개발 클라이언트 대기에서 느린 웜 테이크는 이를 램프합니다. 키와 핫 리로드는 항상 실시간입니다.
  • 정직성: <DemoReel> 캡션의 사실은 테이크의 TUI 행 (dev build 3.5s, Metro 3.9s, dev client 161ms) 및 dev.json (편집 -> ui wait가 새 텍스트를 봄: 0.76s)에서 가져옵니다. 다시 녹화할 때마다 다시 읽으세요; 변경됩니다.
  • 제한 사항: 대시보드에는 별도의 claim 행이 없습니다 (preparing the ios dev build 내부에 있음); --headless이므로 Device Hub 창이 열리지 않음; 100x26 PTY (100열은 HUD의 최소값). stopVideoFirst가 있는 비트는 액션 전에 비디오를 중지합니다 (q는 임대를 종료하고 warden ui는 거부합니다). q 후 테이크는 warden ls가 세션의 임대를 보유하지 않는지 확인하고 (그렇지 않으면 실패), 대시보드가 대체 화면을 떠나는 곳에서 캐스트가 잘려 릴이 마지막 프레임에서 끝납니다. 개발 런처의 흰색 로딩 화면과 파란색 Refreshing… 배너는 실제 앱입니다; Metro 리로드는 다른 에이전트가 apps/example/src를 편집하는 경우에도 발생할 수 있습니다.
  • 램프: ramp는 대기 시간에서 1.2초 헤드와 1초 테일을 뺀 값이 최소 6초(RAMP_MIN_S)인 경우에만 적용됩니다. 히어로는 개발 클라이언트 대기를 태그와 함께 3배로 램프합니다.
  • devEnv는 warden dev 자식에 env를 추가합니다; 히어로는 EXPO_NO_DEPENDENCY_VALIDATION=1 (@expo/cli checkDependenciesOnStart)을 설정하여 Metro 창에서 Expo의 "사용 가능한 업데이트" 알림을 제거합니다. 노이즈에만 사용하고 동작에는 사용하지 마세요.
  • <DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" />로 임베드 (랜딩 히어로, sims/dev.mdx, getting-started/quickstart.mdx). 포스터 = posterBeat 액션 프레임 + 1초 (HUD 켜짐, 모든 행 완료, 앱 실행 중).

9. 터미널 중심 클립 (VID-6)

모든 실제 명령어; 모든 릴은 실행된 것만 보여줍니다. 한 번에 하나의 클립, 각 후에 레이블로 해제.

클립파이프라인명령어
e2e/parallelbatch-take.ts: warden e2e <suite> --count N --record --headless -> <work>/rec, 부팅 시 각 임대된 시뮬레이터를 어둡게 + 9:41 스타일링, 그 다음 compose.ts --height 1080bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2 (--record-only / --compose-only; 헤더는 사양 개수 표시; 카운터는 사양이 작업과 다를 때 N/total shards done을 읽음)
dev/worktreesLanesDemoDefinition: 동시에 각 레인당 하나의 recordDev, 아래에 쉘 스트립 (warden ls)bun run demo:lanes dev/worktrees (lanes.ts, lanes-frame.py, lanes-demo.ts; 레인은 DevTake.startedAtS를 통해 하나의 클록 공유)
golden/cloneDevDemoDefinition.script: PTY에서 warden dev 대신 실행되는 쉘 스크립트bun run demo:dev golden/clone
debug/failurescript + rawVideobun run demo:dev debug/failure --sheet
  • script 모드: 제거 없음, 해제 확인 없음; 테이크는 release --mine --label L --shutdown 및 sims delete <udid> --yes로 종료됩니다. waitBoot가 있는 비트는 PNG가 100KB를 초과할 때까지 simctl io screenshot을 폴링합니다 (HOME_SCREEN_MIN_BYTES): 새로 복제된 헤드리스 시뮬레이터는 claim이 반환된 후 10초 이상 Apple 로고를 표시합니다. 해당 대기를 램프합니다 (3배, 태그 포함).
  • rawVideo: simctl io recordVideo로 녹화합니다 (중지하려면 SIGINT) warden의 ui screen video 도우미가 하나의 장치에서 warden e2e와 충돌하기 때문입니다.
  • --record는 시작 시 존재하는 장치만 녹화합니다; 탄력적인 늦은 참여자는 비디오가 없습니다. 호스트 용량 게이트는 몇 개가 들어맞는지 설정하므로 시작 시 부팅된 시뮬레이터보다 더 많이 요청하지 마세요 (여기서 최대 2개).
  • debug/failure용 임시 사양은 apps/example/e2e/debug-demo.e2e.ts에 작성되고 쉘 트랩에 의해 제거됩니다; 이후 git status 확인 (죽은 테이크는 남길 수 있음).
  • <DemoReel src="/demos/<feature>/<clip>" command="…" facts="…" label="…" />로 임베드; facts는 테이크에서 가져오며, 다시 녹화할 때마다 다시 읽으세요.
  • demo:batch는 폴러로 시뮬레이터 스타일링 (styleWhenBooted: 레이블에 대한 warden ls --json, Booted에 대한 simctl list, 그 다음 ui appearance dark + status_bar override 9:41) warden e2e에는 장치별 훅이 없기 때문입니다; finally에서 --mine --label demo-<feature>-<clip> --shutdown을 해제합니다. 깨끗한 시뮬레이터에서 통과하는 사양을 사용하세요 (perf, settings, todos는 안정적으로 통과하지 않음); compose --height 1080 플래그는 16:9 레이아웃을 제공합니다 (터미널

참고: 원본 문서가 잘린 부분이 있습니다. 나머지 내용은 유사한 패턴으로 계속됩니다.

관련 스킬