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

@makefully/dutifully

v1.20.0

Published

Client tools for Cheerfully service.

Readme

Dutifully (@makefully/dutifully)

Client API for Cheerfully services. Cheerfully makes voice-over easy.

npm install @makefully/dutifully

To use the API, you will need to specify a root ./env-cheerfully.json file with the following format:

{
    "server": "http://server:port",
    "accessToken": "BearerTokenForShowfullyYap"
}

accessToken is sent as Authorization: Bearer … on yap submit/status/download (Showfully 1.4+). For local Showfully, use the same value as SHOWFULLY_DEBUG_TOKEN with SHOWFULLY_ALLOW_DEBUG_TOKEN=1.

Services

All services can be specified in cheerfully.json as described in the following sections. Services can be run as scripts specified in package.json as:

{
    "scripts": {
        "speech": "cheer -service elevenlabs",
        "lipsync": "cheer -service rhubarb",
        "captions": "cheer -service transcription"
    }
}

Alternatively, services can be run from Node by including Dutifully as shown:

const
    cheerfully = require('@makefully/dutifully');

cheerfully({
    service: 'polly'
});

Say (spoken notifications)

say generates short spoken phrases through Cheerfully's ElevenLabs service, caches the audio under .cache/tts/, and plays it locally via node-web-audio-api. Use it from npm scripts instead of wiring ElevenLabs into the consuming project:

{
    "scripts": {
        "say": "say",
        "web:release": "… && npm run say the web release is complete"
    }
}

Or programmatically:

const
    cheerfully = require('@makefully/dutifully');

await cheerfully.say('The build is complete');

Voice settings come from the first elevenlabs entry in cheerfully.json (same as other ElevenLabs chores). Server credentials and elevenLabsApiKey come from env-cheerfully.json. Cached files are keyed by voice + text, so repeats play immediately without regenerating.

ElevenLabs

This text-to-voice service requires a script as input and will download audio VO generated by ElevenLabs. You can set ElevenLabs settings in cheerfully.json like so:

{
    "elevenlabs": [{
        "script": "./example/script.json",
        "output": "./example/vo/en/",
        "model": "eleven_multilingual_v1",
        "updateAllMetaData": true,
        "voice": "21m00Tcm4TlvDq8ikWAM"
    }]
}

You may set a single voice value to an ElevenLabs voice id, or an object of speaker name keys mapped to voice ids. The speaker names must match speakers listed in the script. If "default" is provided as a key, this voice id will be used for any scripts not specifying a speaker.

Specifying the model is optional: if unspecified, Cheerfully tries to look up the voice id and pick a good model; if lookup fails it falls back to eleven_multilingual_v2.

Specifying updateAllMetaData is false by default. If it's set to true, album, title, and unsynchronized lyrics are appended to any MP3's in the source directory that were not generated.

You may include your ElevenLabs API Key directly in the ElevenLabs spec above as apiKey, or set your API Key separately in env-cheerfully.json:

{
    "elevenLabsApiKey": "elevenlabsapikeyforyouraccount"
}

FFMPEG

This audio file process accepts ffmpeg commandline arguments to perform operations on an audio file.

{
    "ffmpeg": [{
        "options": {"ac": 1},
        "src": "./example/vo/en/",
        "output": "./example/vo/en/"
    }]
}

Packfully

This image-to-atlas service packs PNG/JPG/WebP sprites into texture atlases and downloads atlas PNG(s) plus descriptor file(s).

{
    "packfully": [{
        "format": "texturepacker",
        "src": "./example/sprites/",
        "output": "./example/atlases/",
        "options": {
            "maxWidth": 2048,
            "maxHeight": 2048,
            "pot": true,
            "smart": true,
            "square": false,
            "padding": 1,
            "extrude": 1,
            "border": 1,
            "allowRotation": false,
            "trim": true,
            "trimThreshold": 0,
            "prefix": "packed",
            "scale": 1
        },
        "files": {
            "hero.png": { "trim": false }
        }
    }]
}

Use exports: ["texturepacker", "createjs"] (or format) to choose one or more atlas descriptors. Supported formats:

  • texturepacker / json-hash / pixi / phaser-hash — TexturePacker JSON Hash
  • json-array / phaser-array — TexturePacker JSON Array
  • createjs / easeljs — CreateJS SpriteSheet JSON
  • spine — Spine .atlas text
  • css — CSS sprite classes
  • starling / sparrow — Starling/Sparrow XML
  • unity — Unity-oriented JSON
  • godot — Godot atlas JSON

Spacing: padding is empty gap beyond extrude; extrude is edge-pixel bleed into that gap; border is margin from the atlas edge.

Up-to-date atlas PNGs are skipped via an embedded CheerfullyPackfullyHash (source bytes + options). Descriptor files (json/css/atlas/xml) are not hash carriers.

Classfully

This image-to-CSS service approximates still images as a CSS class rule (deterministic mutation search scored via headless Chromium screenshots). Every image under src is processed; files overrides are optional.

{
    "classfully": [{
        "src": "./example/classfully/",
        "output": "./example/classfully/out/",
        "options": {
            "maxIterations": 40,
            "minAccuracy": 0.9,
            "maxMutations": 256
        },
        "files": {
            "swatch.png": {
                "className": "swatch",
                "css": "swatch.css"
            }
        }
    }]
}
  • Omit seed to start from the image’s average background-color; pass seed (declarations or a .css file in src) to refine existing CSS.
  • Output defaults to {stem}.css with .{stem} { … }; override with css / output and className.

Sharp

This still-image service converts, compresses, resizes, center-crops (extract), and can emit multi-size thumbnails or favicon packs (including .ico). Every image under src is processed; files overrides are optional.

{
    "sharp": [{
        "src": "./example/images/",
        "output": "./example/out/",
        "options": {
            "format": "webp",
            "quality": 80,
            "resize": { "width": 1920, "fit": "inside", "withoutEnlargement": true }
        },
        "files": {
            "logo.png": {
                "extract": { "width": 1024, "height": 1024 },
                "format": ["ico", "png"],
                "sizes": [16, 32, 48, 64, 128, 256],
                "prefix": "favicon"
            }
        }
    }]
}

Unchanged outputs are skipped using embedded CheerfullySharpHash metadata (PNG tEXt / GIF comment / JPEG·WebP·TIFF·AVIF XMP). Per-service keys so Sharp and Packfully/Stackfully hashes do not clobber each other. Formats that cannot store metadata (e.g. .ico) are always reprocessed, with a console note.

Stackfully

This multi-layer compositing service builds sized outputs from ordered image layers (positioning, blend, alpha). Every image under src is available as a layer source; define composites under outputs.

{
    "stackfully": [{
        "src": "./example/images/",
        "output": "./example/composites/",
        "options": {
            "format": "png"
        },
        "files": {
            "badge.png": { "composite": true }
        },
        "outputs": {
            "hero.png": {
                "width": 1024,
                "height": 1024,
                "format": "png",
                "layers": [
                    "background.png",
                    {
                        "input": "foreground.png",
                        "alpha": 0.85,
                        "blend": "over",
                        "bottom": 0,
                        "right": 0
                    }
                ]
            }
        }
    }]
}

Unchanged composite outputs are skipped via embedded CheerfullyStackfullyHash (layer source bytes + recipe). Unsupported output types are always reprocessed with a console note.

Polly

This text-to-voice service requires a script as input and will download audio VO generated by Amazon Polly. You can set Polly settings in cheerfully.json like so:

{
    "polly": [{
        "script": "./example/script.json",
        "output": "./example/vo/en/",
        "language": "en-US",
        "updateAllMetaData": true,
        "voice": "Ivy"
    }]
}

You may set a single voice value matching an available AMazon Polly voice or this may be an object of key/value pairs with speaker name keys matched to voices. The speaker names must match speakers listed in the script. If "default" is provided as a key, this voice will be used for any scripts not specifying a speaker.

Specifying updateAllMetaData is false by default. If it's set to true, album, title, and unsynchronized lyrics are appended to any MP3's in the source directory that were not generated.

Allosaurus

This audio-to-lip-flap service requires audio as input and will download a mouth cues JSON file delineating mouth shapes for particular timestamps using Allosaurus. You can set Allosaurus settings in cheerfully.json like so:

{
    "allosaurus": [{
        "options" {
            "compress": true,
            "exportFormat": "json"
        },
        "src": "./example/vo/en/",
        "output": "./example/lipsync/en/"
    }]
}

Export formats include those supported by Rhubarb as well as "mp3" which will embed lipsync timings into the MP3's id3 meta data.

For exporting to JSON, you can set compress to true for the format to use JSON arrays similar to Rhubarb's shorter tsv format.

Rhubarb

This audio-to-lip-flap service requires audio as input and will download a mouth cues JSON file delineating mouth shapes for particular timestamps using Rhubarb Lip-Sync. You can set Rhubarb settings in cheerfully.json like so:

{
    "rhubarb": [{
        "options" {
            "compress": true,
            "exportFormat": "json"
        },
        "src": "./example/vo/en/",
        "output": "./example/lipsync/en/"
    }]
}

Export formats include those supported by Rhubarb as well as "mp3" which will embed lipsync timings into the MP3's id3 meta data.

For exporting to JSON, you can set compress to true for the format to use JSON arrays similar to Rhubarb's shorter tsv format.

Transcription

This audio-to-captions service requires audio as input and will download a captions JSON file delineating captions with timestamps using Amazon Transcription. You can set Transcription settings in cheerfully.json like so:

{
    "transcription": [{
        "format": "sami",
        "language": "en-US",
        "limit": 40,
        "prependSpeaker": "[]",
        "script": "./example/script.json",
        "src": "./example/vo/en/",
        "nowrap": [],
        "output": "./example/captions/en/"
    }]
}

Transcription supports the following formats:

  • JSON
  • LRC
  • MP3 (Embedded in source MP3 as a SYLT ID3 tag labeled "captions".)
  • SAMI / SMI
  • SRT
  • VTT

Specify limit to set the maximum number of characters for a single caption line. The default is 40.

If your scripts include any phrases that should remain unbroken by the captioning process, add them to the nowrap list. For example: "nowrap": ["Makefully Studios"].

Script lines may include an optional class (for example "title" or "stage"). In VTT exports this becomes a cue class span (<c.title>…</c>). In SAMI/SMI the paragraph Class is the job language plus CC (for example ENUSCC for en-US); when a speaker and/or script class is present, the dialog is wrapped in <SPAN Class="…"> (for example Class="todd stage"). In JSON exports it is included on each caption entry. SRT, LRC, and MP3 SYLT ignore it.

Script lines may also include an optional events array for one-shot timed markers. Entries may be:

  • a number — add to the running absolute time offset (ms)
  • a string "<phrase" / ">phrase" — set the offset to just before/after that phrase in the caption (first word may be an incomplete suffix, last word an incomplete prefix); unmatched phrases leave the offset unchanged and are warned/logged for the client; any other string fires that event at the current offset
  • an object { "event", "time"?, "before"?, "after"? } — fires event; optional fields set the absolute offset (time wins over before over after; same phrase edge rules)

Cheerfully resolves these against caption timings and exports them in parallel using the same caption format (events.json, {id}.events.vtt / .srt / .sami / .smi / .lrc, or a separate MP3 SYLT frame with content type EVENTS labeled "events").

Translate

This text-to-text service translates Cheerfully scripts or language-matrix CSV/TSV via Amazon Translate. Set jobs in cheerfully.json:

{
    "translate": [{
        "script": "./example/script.json",
        "language": "en-US",
        "languages": ["es-ES", "fr-FR"],
        "output": "./example/scripts/",
        "ignore": ["Makefully Studios"]
    }, {
        "script": "./example/strings.csv",
        "language": "en-US",
        "languages": ["es-ES", "fr-FR"],
        "output": "./example/scripts/"
    }]
}
  • script — path to a Cheerfully .json script or a language-matrix .csv / .tsv (first column = ids, first row = language tags).
  • language — source locale (CSV: source column). Matrix jobs require this; JSON may use auto.
  • languages — target locales to produce or fill.
  • format / exports — optional. Defaults to the source extension (json, csv, or tsv).
  • ignore — phrases left untranslated.

JSON sources write {lang}/{basename}.{ext} under output. CSV/TSV sources write an updated matrix file and leave existing non-empty cells unchanged.

Supported formats: json, i18n, csv, tsv, po, xliff, properties, yml / yaml, strings, xml.

Example

Find an example script in the ./examples/ folder. You can run either npm run test-elevenlabs or npm run test-polly to create VO tracks in the examples folder that you can then use to try out npm run test-rhubarb and npm run test-transcription. Sample sprites for atlas packing are under ./example/sprites/; run npm run test-packfully to pack them into ./example/atlases/. Run npm run test-translate to translate ./example/script.json and ./example/strings.csv into ./example/scripts/.

These examples are specified using a JSON definition in ./cheerfully.json which you can copy or modify to set different generation parameters as supported by Cheerfully.


Maintainers: see PUBLISHING.md for npm trusted publishing setup.