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

cue-cursor.js

v0.1.5

Published

A browser library for building voice-following teleprompters

Readme

cue-cursor.js

A browser library for building voice-following teleprompters.

Quick start

import { createCue } from 'cue-cursor.js';
import { moonshine } from 'cue-cursor.js/models';

const cue = createCue({
  script: 'The words that should appear in the teleprompter.',
  model: moonshine(),
});

cue.addEventListener('positionchange', ({ detail }) => {
  highlightWord(detail.position);
});

// Diagnostics contain runtime metadata and timings, never audio or text.
cue.addEventListener('diagnostic', ({ detail }) => {
  console.debug(detail);
});

startButton.addEventListener('click', () => void cue.start());
stopButton.addEventListener('click', () => void cue.stop());

start() begins listening and should be called directly from a user gesture so browsers can unlock microphone capture. stop() pauses listening while keeping the Cue instance available for another start().

Call prepare() earlier if the application wants to download and warm the model before the user starts reading.

For lifecycle cleanup, call cue.terminate() from pagehide when the model worker must be stopped synchronously; use the asynchronous cue.destroy() for normal application shutdown so the model can dispose cleanly.

window.addEventListener('pagehide', () => cue.terminate());

Scripts can be plain text or explicit words. Explicit words let a rich-text UI retain its own mapping:

cue.setScript([
  { text: 'Welcome', key: 'word-1' },
  { text: 'back.', key: 'word-2' },
]);

Reading observations

Cue exposes neutral observations so applications can build reading session histories, partial reviews, overlays, or other script-related tools without the library deciding what counts as a mistake.

capturechange reports when microphone capture actually starts and stops. This can differ from the requested listening state when the document is hidden or the model is still preparing. speechactivitychange reports transitions from Cue's audio gate and includes the script position at that moment. It is an estimate of speech activity, not a definitive voice classification.

cue.addEventListener('capturechange', ({ detail }) => {
  console.log('capturing:', detail.active);
});

cue.addEventListener('speechactivitychange', ({ detail, timeStamp }) => {
  console.log('speech activity:', detail.active, detail.position, timeStamp);
});

Each non-empty, non-duplicate transcript includes the matcher's observation. candidatePosition is where the speech matched, while position is the cursor position Cue actually applied. They differ when Cue deliberately holds a small backward match to keep the prompter stable.

cue.addEventListener('transcript', ({ detail, timeStamp }) => {
  const { previousPosition, candidatePosition, position, outcome } = detail.match;
  console.log({ timeStamp, previousPosition, candidatePosition, position, outcome });
});

Event timeStamp values provide the monotonic timeline. Applications own reading session boundaries: stopping Cue can end a capture segment without requiring the application to discard the observations collected so far.

The built-in Moonshine adapter bundles its pinned Transformers.js dependency as a separate, lazy browser asset. The ONNX Runtime WASM binary remains an external download; Transformers.js uses its pinned CDN location by default.

The adapter accepts optional runtimeUrl, modelBaseUrl, and wasmBaseUrl overrides for self-hosting. The library itself does not assume a hosting provider or proxy.

Applications that want the most explicit model boundary can import Moonshine directly. This guarantees that future built-in models cannot enter the same module graph accidentally:

import { moonshine } from 'cue-cursor.js/models/moonshine';

Local demo

The demo is a local development harness, not a hosted application. It contains no Cloudflare configuration, service worker, analytics, or deployment setup.

npm install
npm run dev

Open the local URL printed by Vite, load the sample script, and select Start listening. The first start downloads and warms the default Moonshine model.

Commands

npm test       # unit tests
npm run build  # build and validate the package output
npm run size   # production output sizes: raw, gzip, and Brotli
npm run package:check # run every release check and inspect the package contents