Community코딩 & 개발github.com

ndemo

웹 애플리케이션의 나레이션이 포함된 화면 녹화 데모 비디오를 제작합니다.

ndemo란 무엇인가요?

ndemo는 웹 애플리케이션의 나레이션이 포함된 화면 녹화 데모 비디오를 손쉽게 제작할 수 있는 AI 에이전트 도구입니다. Playwright 기반 브라우저 자동화를 통해 실제 앱을 조작하는 과정을 녹화하고, TTS(Text-to-Speech)를 사용하여 나레이션을 자동 생성한 후 최종 MP4 파일로 렌더링합니다. Claude Code, Cursor, Codex 등 다양한 AI 코딩 에이전트와 함께 사용하도록 설계되었으며, 사용자는 YAML 형식의 플레이북(playbook)을 작성하여 데모의 각 단계(세그먼트)를 정의하고, 각 세그먼트에 대한 나레이션과 브라우저 액션(클릭, 입력, 스크롤 등)을 지정할 수 있습니다. ndemo는 반복 가능한 데모를 위해 설정 단계(setup)에서 파일 복원, 로그인 처리 등을 지원하며, 접근성 트리 기반의 정확한 요소 선택과 '완료 조건(done condition)'을 통해 안정적인 실행을 보장합니다.

지원 대상✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/splitbrain/ndemo/tree/main

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

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

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

문서

ndemo은(는) 무엇을 하나요?

웹 애플리케이션의 나레이션이 포함된 화면 녹화 데모 비디오를 제작합니다.

설정

처음 사용 시, 명령어를 실행하기 전에 반드시 도구 키트를 빌드해야 합니다. ${CLAUDE_SKILL_DIR}/dist/cli.js 파일이 존재하는지 확인하십시오. 없으면 다음을 실행하십시오:

cd ${CLAUDE_SKILL_DIR} && npm install && npm run build

그런 다음 필요에 따라 Playwright 브라우저를 설치하십시오:

cd ${CLAUDE_SKILL_DIR} && npx playwright install chromium

설치를 확인하십시오:

${CLAUDE_SKILL_DIR}/ndemo doctor

명령어

모든 명령어는 ${CLAUDE_SKILL_DIR}/ndemo를 통해 실행됩니다:

명령어설명
${CLAUDE_SKILL_DIR}/ndemo open <playbook>브라우저 데몬 실행, 앱으로 이동
${CLAUDE_SKILL_DIR}/ndemo close브라우저 데몬 종료
${CLAUDE_SKILL_DIR}/ndemo reset앱 URL로 다시 이동 (초기 상태)
${CLAUDE_SKILL_DIR}/ndemo page-state현재 페이지 접근성 트리 출력
${CLAUDE_SKILL_DIR}/ndemo page-state --screenshot동일 + .ndemo/screenshot.png에 스크린샷 저장
${CLAUDE_SKILL_DIR}/ndemo play <playbook>모든 세그먼트 재생
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --segment <id>하나의 세그먼트 재생 (먼저 되감기)
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --from <id>해당 세그먼트부터 끝까지 재생
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --to <id>해당 세그먼트 이후 중지
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --audioTTS 나레이션 오디오와 함께 재생
${CLAUDE_SKILL_DIR}/ndemo render <playbook>전체 파이프라인: TTS → 재생 → 병합 → mp4
${CLAUDE_SKILL_DIR}/ndemo render <playbook> --output <path>특정 출력 파일로 렌더링
${CLAUDE_SKILL_DIR}/ndemo doctor의존성 확인

워크플로우

1단계 — 플레이북 생성

각 플레이북은 사용자 프로젝트의 demo/ 디렉토리 아래에 있는 자체 디렉토리에 위치합니다. 디렉토리 이름은 플레이북 이름과 일치합니다.

demo/
  my-feature/
    my-feature.yaml    ← 플레이북
    fixtures/          ← 설정 중 복원할 파일
    audio/             ← 생성된 TTS 파일 (자동)
    video-raw/         ← 원본 녹화 (자동)
    demo.mp4           ← 최종 출력 (자동)

플레이북을 작성하기 전에, 데모가 안정적이고 반복 가능하게 작동하기 위해 앱이 어떤 상태여야 하는지 생각해보십시오:

  • 사용자가 로그인해야 합니까? → 조건부 로그인 단계 추가
  • 데모가 수정하는 파일/데이터를 복원해야 합니까? → 원본을 플레이북의 fixtures/ 디렉토리에 복사하여 설정에서 복원할 수 있도록 함
  • 데모가 특정 콘텐츠가 존재하는 것에 의존합니까? → fixtures에서 생성하거나 설정 셸 명령어를 통해 생성

데모 중에 수정될 모든 파일을 플레이북 디렉토리의 fixtures/ 하위 디렉토리에 복사하십시오. 설정 단계에서 각 실행 전에 파일을 다시 복사하여 데모가 항상 깨끗한 상태에서 시작되도록 합니다.

디렉토리와 YAML 파일을 생성하십시오. 전체 플레이북 스키마는 다음과 같은 최상위 섹션을 지원합니다:

# demo/edit-page/edit-page.yaml
app:
  url: http://localhost:8080/wiki
  viewport:                          # 선택 사항, 기본값 1920x1080
    width: 1920
    height: 1080
  scale: 2                           # 장치 배율 (기본값 2)
  zoom: 1.25                         # CSS 확대/축소 수준 (기본값 1.25)
  colorScheme: light                 # "light" 또는 "dark" (기본값 "light")
  setup:
    # 데모 중 수정된 파일 복원
    - run: cp demo/edit-page/fixtures/page.txt data/pages/page.txt
    # 이전 실행의 아티팩트 정리
    - run: rm -f data/cache/*.tmp
    # 필요한 경우 로그인 (조건부 — 이미 로그인된 경우 건너뜀)
    - type: click
      target: { role: link, name: "Login" }
      if:
        hidden: ".user-info"
    - type: type
      target: { role: textbox, name: "Username" }
      text: admin
      if:
        visible: ".login-form"
    - type: type
      target: { role: textbox, name: "Password" }
      text: password
      if:
        visible: ".login-form"
    - type: click
      target: { role: button, name: "Sign in" }
      if:
        visible: ".login-form"
      done:
        visible: ".user-info"

titleCard:                               # 선택 사항 제목 카드 (첫 번째 프레임으로 표시)
  title: "Editing a Wiki Page"           # 눈에 띄게 표시
  subtitle: "A quick tour of the editor" # 선택 사항 부제목
  duration: 3000                         # 밀리초 단위 유지 시간 (기본값 3000)

tts:                                   # 선택 사항 TTS 설정
  provider: openai                     # "openai" (기본값) 또는 "elevenlabs"
  voice: alloy                         # TTS 음성 이름 (기본값 "alloy")
  speed: 1.0                           # 음성 속도 배수 (기본값 1.0)

recording:                             # 선택 사항 녹화 설정
  outputDir: "."                       # 플레이북 기준 출력 디렉토리 (기본값 ".")
  fps: 30                              # 비디오 프레임 속도 (기본값 30)

segments:
  - id: intro
    narration: "Welcome to our wiki. Let's edit a page."
    intent: "show the wiki start page"
    timing: after                      # "after" (기본값) 또는 "parallel"
    actions:
      - type: wait
        duration: 2000

  - id: open-editor
    narration: "Click the edit button to open the editor."
    intent: "click the edit button"
    actions: []

**세그먼트 타이밍(timing)**은 나레이션에 대한 액션 실행 시점을 제어합니다:

  • after (기본값) — 나레이션이 먼저 재생된 후 액션이 실행됩니다.
  • parallel — 나레이션이 재생되는 동안 액션이 실행됩니다.

먼저 모든 세그먼트를 나레이션(narration)과 의도(intent)와 함께 작성하십시오. 액션(actions)은 빈 배열로 남겨둡니다. 플레이북 경로를 ndemo 명령어에 전달할 때는 절대 경로를 사용하십시오.

2단계 — 브라우저 열기

${CLAUDE_SKILL_DIR}/ndemo open /absolute/path/to/demo/my-demo/my-demo.yaml

3단계 — 각 세그먼트 작성

빈 액션이 있는 각 세그먼트에 대해:

a) 현재 페이지 상태를 읽습니다:

${CLAUDE_SKILL_DIR}/ndemo page-state

b) 접근성 트리 출력을 확인합니다. 세그먼트의 의도(intent)에서 참조하는 요소를 찾습니다. 트리의 요소를 사용하여 플레이북 YAML에 액션을 작성합니다.

대상(target) 작성 방법 — page-state 출력의 정보를 사용하십시오:

page-state에 [button "Settings"]이 표시되면:

target: { role: button, name: "Settings" }

page-state에 [searchbox "Search reports" value=""]이 표시되면:

target: { role: searchbox, name: "Search reports" }

기타 대상 필드 — 요소를 가장 잘 식별하는 것을 사용하십시오:

target: { selector: "#my-element" }        # CSS 선택자
target: { testId: "submit-btn" }           # data-testid 속성
target: { label: "Email" }                 # aria-label
target: { placeholder: "Search..." }       # placeholder 텍스트
target: { text: "Click me" }              # 표시되는 텍스트 내용

하나 이상의 대상 필드가 필요합니다. 여러 필드를 결합하여 일치 범위를 좁힐 수 있습니다.

팁: 웹 앱의 소스 코드는 프로젝트 저장소에서 사용할 수 있습니다. 접근성 트리만으로 충분하지 않은 경우 구성 요소 소스에서 data-testid 속성, 클래스 이름 또는 ID를 찾아보십시오.

완료 조건(done conditions) 작성 방법:

done:
  visible: ".settings-panel"        # 요소가 나타남
  hidden: ".loading-spinner"        # 요소가 사라짐
  networkIdle: true                 # 보류 중인 요청 없음
  stable: 500                       # 500ms 동안 DOM 변경 없음
  url: "**/settings"                # URL 변경됨
  text:                             # 요소가 텍스트를 포함함
    selector: ".results"
    has: "Q3 Revenue"
  attribute:                        # 요소가 속성을 가짐
    selector: html
    name: data-theme
    value: dark
  timeout: 10000                    # 기본 제한 시간 재정의 (ms)

페이지를 변경하는 모든 액션에는 반드시 완료 조건(done condition)이 있어야 합니다. 그렇지 않으면 페이지가 준비되기 전에 다음 액션이 실행될 수 있습니다.

시각적 변경 후에는 wait 액션을 추가하여 시청자가 변경된 내용을 볼 수 있도록 하십시오:

- type: wait
  duration: 2000    # 2초

c) 세그먼트를 테스트합니다:

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml --segment <id>

d) 실패하면 ${CLAUDE_SKILL_DIR}/ndemo page-state를 실행하여 현재 화면에 무엇이 있는지 확인하고, 액션을 조정한 후 다시 시도하십시오.

e) 세그먼트가 작동한 후, 다음 세그먼트를 작성하기 전에 page-state를 다시 읽으십시오 — 페이지가 변경되었습니다.

4단계 — 검토

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml

브라우저에서 전체 시퀀스를 시청하십시오. 사용자에게 올바른지 물어보십시오.

TTS 나레이션과 함께 검토하려면 (OPENAI_API_KEY 및 ffplay 필요):

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml --audio

이 명령은 각 세그먼트에 대한 TTS 오디오를 생성하고 (콘텐츠 해시로 캐시됨), 브라우저 액션과 함께 오디오를 재생하며, 액션이 나레이션 길이와 일치하도록 타이밍을 조정합니다. 나레이션 텍스트가 변경되면 오디오가 자동으로 다시 생성되고 이전 파일은 삭제됩니다.

5단계 — 반복

사용자가 변경을 요청할 수 있습니다. 일반적인 패턴:

사용자 말수행할 작업
"잘못된 버튼"page-state 실행, 올바른 요소 찾기, 대상 업데이트
"너무 빠름" / "너무 느림"대기 시간 조정, --audio로 다시 재생하여 타이밍 확인
"나레이션을 ...로 변경"narration 필드 편집
"X를 보여주는 단계 추가"새 세그먼트 삽입, 액션 작성
"그 단계 제거"YAML에서 세그먼트 삭제
"세그먼트 X부터 다시 재생"${CLAUDE_SKILL_DIR}/ndemo play --from <id>
"처음부터 다시 시작"${CLAUDE_SKILL_DIR}/ndemo reset 후 다시 재생

각 변경 후, 영향을 받는 세그먼트를 다시 재생하여 확인하십시오.

6단계 — 렌더링

사용자가 승인하면:

${CLAUDE_SKILL_DIR}/ndemo render /absolute/path/to/demo/my-demo/my-demo.yaml

이 명령은 TTS 나레이션이 포함된 최종 mp4 파일을 생성합니다.

설정 단계 참조

설정 단계는 데모가 시작되기 전에 실행됩니다 (open, reset, play, render에서). 셸 명령어 또는 브라우저 액션이 될 수 있습니다.

셸 명령어는 프로젝트 작업 디렉토리(ndemo가 호출된 디렉토리)를 기준으로 실행됩니다. run 명령어의 모든 경로는 프로젝트 루트를 기준으로 해야 합니다.

셸 명령어 — 파일 작업에는 run을 사용하십시오:

setup:
  - run: cp demo/my-feature/fixtures/original.txt data/page.txt
  - run: rm -f data/cache/*.tmp
  - run: ./scripts/reset-db.sh

브라우저 액션 — 세그먼트 액션과 동일한 구문:

setup:
  - type: click
    target: { role: button, name: "Login" }
  - type: type
    target: { role: textbox, name: "Username" }
    text: admin

조건부 단계 — if를 추가하여 조건이 충족되지 않으면 건너뜁니다:

setup:
  # 이미 로그인된 경우에만 로그인
  - type: click
    target: { role: link, name: "Sign in" }
    if:
      hidden: ".user-menu"      # 사용자 메뉴가 표시되면 건너뜀
  # 특정 페이지에서만 실행
  - type: click
    target: { role: button, name: "Reset" }
    if:
      url: "**/admin/settings"  # 이 페이지가 아니면 건너뜀

조건 필드:

  • visible: "<selector>" — 선택자가 표시되는 요소와 일치하는 경우에만 단계 실행
  • hidden: "<selector>" — 선택자가 표시되는 요소와 일치하지 않는 경우에만 단계 실행
  • url: "<pattern>" — 현재 URL이 일치하는 경우에만 단계 실행 (** = 모든 경로)

액션 유형 참조

유형필수 필드참고
clicktarget
typetarget, text선택 사항 delay (ms) 키 입력 간 지연 시간 (기본값 80)
hovertarget
scrolltarget요소가 보일 때까지 스크롤
waitduration (ms)시청자를 위한 일시 중지 (기본값 1000ms)
presskey키보드 키, 예: "Enter", "Escape"
selecttarget, option드롭다운 선택

세그먼트 ID 규칙

  • 소문자 영숫자와 하이픈 사용: open-settings, toggle-dark
  • 플레이북 내에서 고유해야 함
  • 오디오/비디오 파일 이름으로 사용되므로 짧게 유지하십시오.

문제 해결

  • 브라우저가 응답하지 않음: ${CLAUDE_SKILL_DIR}/ndemo close를 실행한 후 다시 open하십시오. 브라우저 데몬 출력은 .ndemo/daemon.log에서 확인하십시오.
  • 요소를 찾을 수 없음: ${CLAUDE_SKILL_DIR}/ndemo page-state를 실행하여 현재 화면의 접근성 트리를 확인하고 대상(target)을 업데이트하십시오.

관련 스킬