npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scenario-kit

v0.9.0

Published

Record product demo videos with Playwright and composite them into branded mp4s with Remotion, driven by a declarative JSON scenario.

Readme

scenario-kit

Record a product demo with a real browser (Playwright), then composite it into a branded mp4 (Remotion) — intro card, rounded browser window, outro card. Drive it from a declarative JSON scenario, no code required.

First run downloads a Chrome build for rendering and, if needed, Chromium for Playwright — expect a multi-hundred-MB one-time download.

Quick start

npx scenario-kit init             # scaffold scenario-kit/config.json + scenario-kit/scenarios/landing.json
npx playwright install chromium   # once per machine
npx scenario-kit run landing      # record + render -> scenario-kit/out/landing-demo.mp4

ffmpeg and ffprobe must be on PATH (used to convert the Playwright recording to h264 before compositing).

Project layout

Everything lives in a scenario-kit/ directory at your project root:

scenario-kit/
  config.json     brand + output settings
  scenarios/
    landing.json  a recording scenario (add more: scenario-kit/scenarios/<name>.json)
  assets/         brand assets such as logo.png (referenced from config.json)
  out/            generated recordings, screenshots and mp4s (gitignored by init)

scenario-kit/config.json:

{
  "brand": {
    "name": "...",                    // required, unless "logo" is set (then optional: wordmark shows the logo alone)
    "tagline": "...", "url": "...", "bg": "#1E293B", "accent": "#6366F1", "text": "#F8FAFC",
    "logo": "assets/logo.png"         // optional image (png/svg/...) shown instead of the generated initial icon,
  },                                  // resolved relative to scenario-kit/
  "outDir": "out",                    // optional, default "out" (relative to scenario-kit/)
  "storageState": ".auth/state.json", // optional, a Playwright storageState file for logged-in demos
  "intro": true,                      // optional, default true; set false to drop the intro card
  "outro": true                       // optional, default true; set false to drop the outro card
}

Writing a scenario

scenario-kit/scenarios/<name>.json has a steps array. Each step is a single-key object:

| step | argument | effect | | --- | --- | --- | | goto | url | navigate to a URL | | click | locator | click a Playwright locator | | type | [locator, text] | type text into a locator | | move | [x, y] | move the mouse cursor | | scroll | y | smooth-scroll to a Y offset | | pause | ms | wait | | waitFor | locator | wait for a locator to appear | | mark | label | record a named timeline marker | | highlight | locator | draw a red highlight box around a locator (shots only, no-op in record/smoke) | | screenshot | label | capture the current viewport as a PNG, then clear highlights (shots/smoke only, no-op in record) |

locator is any Playwright locator string (text=Get started, #hero, [data-testid=cta], ...). Unknown step keys are rejected before recording starts. See npx scenario-kit --help for the full reference, or schema/scenario.schema.json for editor validation and autocomplete (referenced by the $schema field init writes into landing.json).

For interactions the JSON vocabulary can't express, write scenario-kit/scenarios/<name>.ts instead (resolved when no matching <name>.json exists). It just needs a default-exported async function — no import of scenario-kit required, so this works with npx scenario-kit alone, even when your project has no dependency on scenario-kit:

export default async ({ page, mark }) => {
  await page.goto('https://example.com');
  mark('hero');
};

If scenario-kit is a dependency of your project, wrap it in defineScenario for typed page/mark parameters — it's an identity function, purely for type-checking:

import { defineScenario } from 'scenario-kit';

export default defineScenario(async ({ page, mark }) => {
  await page.goto('https://example.com');
  mark('hero');
});

macOS app scenarios (desktop apps, e.g. Claude Desktop)

record/render/run can also drive a native macOS app instead of a browser — shots/smoke don't support this yet. Add a top-level app key naming the app (as used by open -a / System Events):

{
  "app": { "name": "Claude", "width": 1440, "height": 900 },
  "steps": [
    { "keystroke": "cmd+n" },
    { "type": "こんにちは、今日の天気は?" },
    { "keystroke": "enter" },
    { "pause": 3000 },
    { "mark": "reply" }
  ]
}

width/height are the window size in points, default 1440x900. App scenarios use their own steps vocabulary (JSON only — no .ts escape hatch yet):

| step | argument | effect | | --- | --- | --- | | keystroke | "cmd+n" | modifiers (cmd/shift/ctrl/opt) joined with +, then enter/esc/tab/space or a single alphanumeric key | | type | text | type Unicode text into the focused element | | click | [x, y] | move (eased) then click a window-relative point (pt) | | move | [x, y] | move the cursor to a window-relative point (pt), no click | | pause | ms | wait | | mark | label | record a named timeline marker |

Requires macOS, ffmpeg on PATH (screen capture, not just conversion), and cliclick on PATH (brew install cliclick). Grant your terminal app Accessibility permission (System Settings → Privacy & Security → Accessibility) before the first run — record preflights this and fails fast with instructions otherwise. Screen-recording permission can't be preflighted; macOS prompts for it the first time ffmpeg captures the screen, and that prompt shows up in the recording itself. Run record once on a throwaway scenario first to grant the permission, then record for real. The rendered video uses a bare window frame (rounded corners + shadow, no fake browser bar) since the real app window already has its own title bar.

Logged-in demos (authentication)

To record pages behind a login, save a logged-in session once:

npx scenario-kit login https://example.com/login

A browser opens — log in manually, then press Enter in the terminal. The session (a Playwright storageState file: cookies + localStorage) is saved to scenario-kit/.auth/state.json (git-ignored automatically) or to the configured storageState path. Point storageState in scenario-kit/config.json at it:

{ "storageState": ".auth/state.json" }

Recordings then start already logged in — no login steps in the scenario, no credentials on disk beyond the session file. When the session expires (the recording suddenly shows a login page), run scenario-kit login again.

Screenshots

scenario-kit shots <name> runs the same scenario as record, but captures PNG screenshots instead of a video — no ffmpeg/Remotion involved, and no pseudo-cursor. Use highlight and screenshot steps to annotate:

{
  "steps": [
    { "goto": "https://example.com" },
    { "highlight": "text=Get started" },
    { "screenshot": "hero" }
  ]
}

Each screenshot step writes the current viewport (and clears any pending highlights afterward) to scenario-kit/out/shots/<name>/01-hero.png (numbered in capture order, label sanitized for the filename). The output directory is wiped and recreated at the start of each shots run. highlight/screenshot are no-ops during record, so the same scenario can drive both a demo video (no red boxes) and release-note screenshots (with them).

Smoke

scenario-kit smoke <name> runs the scenario like record (with the pseudo-cursor), but also watches the page for runtime issues and writes a structured report instead of compositing a branded video — a light verification pass that leaves reviewable evidence (video, screenshots, report):

scenario-kit/out/smoke/<name>/
  video.mp4       plain h264 recording (for a human to skim, no intro/outro)
  report.json     structured result — see below
  01-hero.png     checkpoint PNGs from `screenshot` steps (same numbering as shots)
  issue-1.png     auto-captured screenshot at the moment an issue was detected
  failure.png     auto-captured screenshot if a step itself failed

It collects 4 kinds of issues while the scenario runs: console-error (console.error output), page-error (an uncaught exception), http-error (a response with status >= 400 for a document/xhr/fetch request — other resource types like images/fonts are ignored as noise), and request-failed (a network-level failure). Each issue records the step index (JSON scenarios only) and the most recent mark label for context. Issue screenshots are capped (one per step index, or 10 total when the step index isn't available, e.g. TS scenarios) to avoid flooding the output. highlight is a no-op in smoke (it would show up in the recording); screenshot steps work the same as in shots. The output directory is wiped and recreated on each run, and smoke requires ffmpeg/ffprobe on PATH like record/run.

report.json:

{
  "name": "landing",
  "ok": false,
  "status": "fail",
  "video": "video.mp4",
  "scenarioType": "json",
  "steps": [{ "index": 0, "step": { "goto": "..." }, "status": "ok" }],
  "failure": { "stepIndex": 4, "message": "...", "url": "https://...", "screenshot": "failure.png" },
  "issues": [
    { "type": "console-error", "message": "...", "pageUrl": "https://...", "stepIndex": 3, "mark": "hero", "screenshot": "issue-1.png" }
  ]
}

status is one of pass / fail / inconclusive, and ok is true only for pass (kept for backward compatibility):

  • pass (exit 0): the scenario completed without a step failure and with zero detected issues.
  • fail (exit 2): a step failed or at least one issue was detected — read report.json and the referenced screenshots to see what went wrong.
  • inconclusive (exit 3): the scenario could not be evaluated for an environmental reason, so this is not an app regression. It covers setup failures (ffmpeg/ffprobe missing, browser/context launch failure) and a connection-class navigation failure (net::ERR_CONNECTION_REFUSED, ERR_CONNECTION_RESET, ERR_NAME_NOT_RESOLVED, ERR_ADDRESS_UNREACHABLE, ERR_CONNECTION_TIMED_OUT) that happened before any page was successfully evaluated — typically a dev server that isn't running. A reason field carries the underlying message. A connection failure after a page has loaded, and a navigation/locator Timeout ... exceeded, both stay fail. report.json is still written (even on a pre-launch failure).

Commands

scenario-kit init                     scaffold scenario-kit/ in the current project
scenario-kit record <name>            record a scenario to scenario-kit/out/recordings/<name>.webm
scenario-kit render <name>            convert + composite into scenario-kit/out/<name>-demo.mp4
scenario-kit run <name>               record + render
scenario-kit shots <name>             capture PNG screenshots to scenario-kit/out/shots/<name>/ (no video, no ffmpeg)
scenario-kit smoke <name>             record + detect runtime issues, writing scenario-kit/out/smoke/<name>/{video.mp4,report.json,*.png}
scenario-kit login [url]              log in manually in a browser, save the session for logged-in demos
scenario-kit install-skill            install the scenario-kit SKILL.md into .claude/skills/ and .agents/skills/
scenario-kit install-skill --user     install into ~/.claude/skills/scenario-kit/ instead
scenario-kit --help                   full command and steps reference

Exit codes: 0 success, 1 invalid config/scenario, 2 runtime failure (browser, ffmpeg, or render error). smoke additionally uses 2 for a detected app failure and 3 for an inconclusive run (environmental — see the Smoke section).

AI agent skill

scenario-kit install-skill drops a SKILL.md into your project so Claude Code / Codex-style agents can regenerate demo videos on request — it documents npx scenario-kit usage and the steps vocabulary, with no bundled scripts.

Notes

  • Not (yet) handled: click-synced zoom, background music, a bundled ffmpeg, Windows.