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

demovid

v0.5.0

Published

Rode dentro do seu projeto frontend, descreva a demo em português, e receba um MP4 narrado + uma timeline com os tempos de cada fala e os melhores pontos de corte. O roteiro é escrito por IA a partir dos elementos reais do app.

Readme

demovid

Turns any frontend project into a narrated demo video. One pass, no post-production.

A Playwright-driven Chromium loads your app and an injected overlay draws speech balloons, a synthetic cursor and a spotlight. Narration is pre-rendered with OpenAI TTS and played through the speakers; a screen recorder captures the browser window with system audio. Because the audio plays live and audio.onended drives step advance, there is no timeline to synchronise — no forced alignment, no word timestamps, no drift.

cd ~/my-app
npx demovid                # scan → ask what to demo → gpt-5.4 writes it → rehearse → record

It works out how to run your project, starts (or adopts) your dev server, crawls the app for elements it can actually address, asks in Portuguese what you want to show, has gpt-5.4 write the storyboard, rehearses it so broken selectors surface before anything is recorded, and only then records. You get demo.mp4 and demo.timeline.json.

Working out how to run your project

Most projects are answered for free, by reading files: the manifests of every workspace member (your frontend is often not in the root package.json), the server.port in vite.config.ts and friends, and the URL your dev server prints on startup — whichever of those is actual evidence rather than a guess.

When that is not enough, demovid hands the repository to the pi coding agent (deepseek-v4-pro, thinking xhigh), which reads the README, follows the dev script, and answers four things nothing else can: how to start the app and at which URL, whether it needs a login and which dev credentials the repo documents, how the app shows that it is busy — the spinners, skeletons and aria-busy flags a Playwright script has to wait out, plus which operations are slow enough to need a bigger ceiling — and what is actually worth demonstrating, offered as a ready-made answer you accept with Enter or type over. The answer is cached in .demovid.json, so it is asked once.

The answer is written to .demovid.json in your project, so it is paid for once, and it is plain JSON you can correct by hand. It is re-derived when your manifests change, not on a timer.

npx demovid --no-discover     # never call the agent; fail if the files do not suffice
npx demovid --url http://localhost:5273   # skip configuration entirely

The agent runs with all of its tools, including write, so it can edit the repository it is inspecting. demovid diffs the git working tree around the call and names anything it touched — it cannot undo those edits for you. Use --no-discover or --url if that is not a trade you want.

The hand-written path still works, and the guided flow just writes the same file:

demovid doctor                      # does this machine have what it needs?
demovid rehearse demo.yaml          # validate selectors and camera, record nothing
demovid record   demo.yaml --res reels   # → demo.mp4 + demo.timeline.json
# examples/demo.yaml
title: Painel de Exames
url: ./app.html          # relative to the YAML
locale: pt-BR
preset: boardroom

steps:
  - action: focus
    target: "#kpi-pendentes"
    say: Aqui em cima ficam os indicadores do dia.
  - action: type
    target: "#busca"
    value: "REQ-88413"
    say: A busca aceita o protocolo ou o nome do paciente.

A GIF for a README, with no voice at all

demovid record demo.yaml --format gif           # → demo.gif, ≤ 5 MB, no API calls for audio
demovid record demo.yaml --format webp          # same pass, usually 5–10× smaller

--format gif (or webp) is a different product, not the MP4 with the sound off:

  • No narration is synthesised. Not muted — never requested. A silent take costs nothing in TTS.
  • The balloon becomes the only channel, so the storyboard's caption field is used instead of say (falling back to say when a step has none), and the readme preset makes it big enough to read: 21px, slight transparency over a 3px backdrop blur, and placement that keeps clear of the cursor as well as of the element it describes.
  • Step timing comes from the reading budgetpacing.cps and the dwell floors — because there is no onended to wait for.
  • 5 MB is a hard ceiling. Over it, frames are dropped (15 → 12 → 10 → 8 → 6 → 5 fps) until the file fits, and the log says which rung shipped. If even 5 fps is too big it hands you the file with a loud warning rather than nothing.

Measured on a 4 s 1280x720 source: GIF 1.08 MB, WebP 0.10 MB. GitHub renders both in READMEs, issues and PR comments, so webp is the better default whenever the audience is GitHub only; gif is what still works in npm, PyPI and older wikis.


Status

Working: the guided flow, doctor, rehearse, record, restore. Presets boardroom, helpdesk and readme, locale pt-BR. Output as MP4, GIF or animated WebP. Resolutions from 720p to reels (9:16 vertical), plus any of Playwright's 207 device names.

Requirements

  • Linux/X11. Window capture is native there. Wayland falls back to the desktop portal and is untested.
  • Node ≥ 20, a Chromium-family browser (Brave, Chrome, Chromium — Firefox will not work, no CDP), ffmpeg and xdotool.
  • Two API keys, split by job rather than by vendor. OPENAI_API_KEY writes the storyboard and the commercial edit (gpt-5.4) and synthesises the narration. DEEPSEEK_API_KEY is spent only by the pi agent below. Recording a hand-written storyboard with --format gif|webp needs no key at all — no prose to write, no narration to synthesise.
  • The pi coding agent — the only thing that spends DEEPSEEK_API_KEY, and it is called only when demovid cannot work out how to run your project from its files, which is the case for anything with a non-standard dev server. A project it recognises never calls it, and --no-discover or --url opt out entirely.
  • A screen recorder — optional. demovid prefers gpu-screen-recorder and drives it directly; there is no wrapper script to install. Without it, it falls back to ffmpeg (x11grab + the PulseAudio monitor, with NVENC or VAAPI when available). The fallback cannot pause and cannot follow a window that moves, and it says so rather than pretending otherwise.

Run demovid doctor — it names the backend it picked and why.

What comes out

Two files. The MP4, and a .timeline.json that demovid can produce and a screen recorder cannot, because demovid caused everything in the frame:

{ "clock": { "method": "first-frame-ts", "residualMs": 17 },
  "narration": [{ "text": "Aqui em cima ficam os indicadores…",
                  "startMs": 15341, "endMs": 18805, "measured": true }],
  "events":    [{ "t": "camera-move", "startMs": 14980, "endMs": 15430 }],
  "cuts":      [{ "atMs": 33174, "score": 0.75, "kind": "entre-passos",
                  "reasons": ["silêncio de 4375ms (+0.35)", "câmera parada (+0.25)",
                              "fronteira de passo (+0.15)"] }] }

clock.method says which anchor was trusted and residualMs is the honest error bar on every timestamp in the file. Cut scores carry their derivation, because a score you cannot explain is a score you cannot tune.

Run demovid doctor — it checks every one of these and tells you which is missing. Add --deep to spend one minimal API call proving the OpenAI key has credit, not just that it is valid: /v1/models is free and returns 200 on an account with zero balance.

How it works

┌─ recorder ── captures the browser window + system audio ─────────┐
│ ┌─ Chromium (disposable profile, headed) ──────────────────────┐ │
│ │  document.documentElement                                    │ │
│ │   ├── <div id=__demovid_stage>  ← THE STAGE, position:fixed  │ │
│ │   │     translate3d(…) scale(k) · transform-origin 0 0        │ │
│ │   │     (the whole app was moved in here)                    │ │
│ │   └── <div popover=manual>      ← THE OVERLAY, top layer     │ │
│ │         + shadow root. Balloon · cursor · mask · audio       │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

The overlay never scales. Top-layer elements escape ancestor transforms — CSS Position L4 §3: they "generate boxes as if they were siblings of the root element… Ancestor elements with overflow, opacity, mask, etc. cannot affect it." But the balloon still follows the zoom for free, because getBoundingClientRect() on an element inside the stage already returns the transformed rect.

Things this project measured, so you do not have to

Most of these contradict what the surrounding literature says. Every one is reproducible from the tests in test/.

A port is free on IPv4 and taken on IPv6 at the same time. Vite 7 bound [::1]:5273 and nothing on 127.0.0.1, so a connect probe against IPv4 alone reported the port free while the app was serving 200 — and demovid started a second dev server against a port that was already in use. Plenty of servers bind IPv4 only, so neither family can be the one you ask: probe both and OR the result.

"Believe the URL the server prints" is not implementable as a fallback. The announcement has to race the guessed port, not be consulted after waiting on it — if you wait first, the timeout expires before you ever read the announcement, which is precisely the case it was written for. But it cannot simply win, either: a dev script that starts an API and a frontend announces whichever printed first, and an orphaned process from an earlier run answers on it instantly. A port read from vite.config.ts therefore gets a head start; only after it expires does an announcement count.

Transforming document.body does not work. It is what the existing open-source tools in this space do, and it is broken: under a transformed body, position: fixed behaves like absolute. A header scrolls to y=-800 after scrolling 800px, bottom: 0 resolves against the content height (y=4004 instead of 669), and height: 100% becomes the whole document. body is viewport-width but content-height — only half of "coincident with the viewport" is true. The stage has to be a position: fixed element instead.

scrollbar-width: none on the stage is structural, not cosmetic. The moment any transform is set — even scale(1) — a position: fixed; left:0; right:0 header switches containing block from the viewport to the stage's padding box, and a visible scrollbar makes that box 15px narrower. The header silently goes 1353 → 1338px.

transform: scale() does not blur text. Captured the real window mid-zoom at k=1.4/2/3 and magnified 200% with nearest-neighbour: clean glyph edges in both a WAAPI and a rAF-driven camera. What does blur permanently is will-change: transform — it pins the raster scale. Never use it here.

OpenAI TTS narrates imperatives correctly. A widely-repeated claim says gpt-4o-mini-tts answers commands instead of reading them, which would be fatal for demo scripts. Transcribing the output and comparing to the input gives 100% fidelity, with and without instructions.

instructions controls pace up to ~140 wpm, then saturates. Asking for 130 delivered 129; asking for 170 still delivered 140. Above that the only working lever is speed, which is reliable here and linear (1.15→1.19×, 1.30→1.32×, 1.50→1.58×).

Trim edge silence — with one clip per sentence it is worth ~840 ms each. Measured on a single long clip it looks like 80 ms and not worth doing. That is the wrong measurement for this architecture: short clips carry proportionally huge padding, about 17 s of dead air across a 20-sentence script — and since onended drives advance, that is 17 s of frozen screen. Trimming took a clip from 101 wpm to 140 wpm, and makes the gap between steps a preset knob instead of an accident.

Development

npm install
npm run verify   # typecheck + bake springs + build + unit tests + 2 browser e2e (one records)

The e2e tests open a real browser and one of them records ~8 s of real video. They are not mocked because the things worth proving — that the top layer escapes a transform, that a position: fixed header survives the scroller swap, that audio actually reaches the file — only exist in a real engine.

Prior art

This space got crowded in 2026. Read these before assuming demovid is the right tool for you: playwright-recast, argo, shot-scraper video, Cap.

demovid's difference is the single pass: OS-level capture at 60 fps with GPU encoding, and live audio playback instead of a post-production timeline.

Ideas and constants borrowed with thanks — see NOTICE.

License

Apache-2.0. See LICENSE.