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

dead-simple-dice

v0.2.7

Published

A tiny, dependency-free CLI dice roller (dsd) for tabletop, shell, scripts, and pipes.

Downloads

576

Readme


What it is

Dead-Simple-Dice is a small Node.js CLI that installs the command dsd.

It rolls simple dice notation and gets out of the way. The default output is deliberately boring so it works in shells, scripts, pipes, and tabletop notes:

dsd d20
# 17

# four six-sided dice
dsd 4d6
# 3 5 1 6

# pipe notation in
echo d20 | dsd
# 12

No ads. No accounts. No network calls. No telemetry. No runtime dependencies.


Quick start

git clone https://github.com/M0KA907/Dead-Simple-Dice.git
cd Dead-Simple-Dice
./install.sh

dsd d20
dsd 4d6
echo d20 | dsd

For terminal flair, opt in explicitly:

dsd 4d6 --ui
dsd 4d6 --cyber

Piped or redirected output always stays plain, even when display effects are enabled.


At a glance

| Thing | Value | |---|---| | Command | dsd | | Package | dead-simple-dice | | Version | 0.2.0 | | Runtime | Node.js ESM | | Minimum Node | >=18 | | Runtime deps | 0 | | Default output | Plain script-safe text | | Fancy output | Opt-in TUI / cyber modes | | Random source | Node crypto.randomInt | | Test runner | Built-in node:test | | License | MIT | | Registry status | Not published to npm yet |


Highlights

| Need | What dsd does | |---|---| | Roll fast | dsd d20, dsd 4d6, dsd d6 -n 4 | | Pipe cleanly | Reads stdin line-by-line and prints plain results | | Stay script-safe | No ANSI, boxes, or animation in non-TTY output | | Look good in a terminal | Optional --ui and --cyber modes | | Stay predictable | Small notation, bounded values, clear errors | | Stay offline | Zero runtime dependencies, no network calls |


Install

Dead-Simple-Dice is currently installed from a local git checkout. Registry installation is future work.

Fresh install

git clone https://github.com/M0KA907/Dead-Simple-Dice.git
cd Dead-Simple-Dice
./install.sh

./install.sh delegates to ./scripts/install.sh, which checks Node and npm, then runs npm ci, npm test, npm link, and verifies dsd --version plus dsd d20. It fails fast with a useful error, needs no sudo, and does not touch your shell startup files.

Update an existing checkout

For a local git checkout, pull and relink/retest in one step:

cd Dead-Simple-Dice
./update.sh

./update.sh delegates to ./scripts/update.sh. It verifies you are in a git repo, shows the current branch, then runs git pull --ff-only, npm ci, npm test, npm link, and re-verifies dsd. It only fast-forwards — it never merges, rebases, resets, or force-pulls. If your working tree has uncommitted changes it stops and tells you to commit, stash, or restore them yourself first; it never auto-stashes or deletes your changes.

If dsd is not found

npm link installs the command into your npm global bin directory. If the link succeeds but your shell cannot find dsd, add that directory to PATH.

For example, if npm config get prefix prints $HOME/.local/npm, add:

export PATH="$HOME/.local/npm/bin:$PATH"

Put that line in ~/.bashrc, ~/.zshrc, or your shell's equivalent startup file, then open a new terminal or source the file.

Uninstall

./uninstall.sh

./uninstall.sh delegates to ./scripts/uninstall.sh, which runs npm unlink -g dead-simple-dice. This removes the global linked dsd command but does not delete the repo — remove the cloned directory yourself if you want the source gone too.

Manual fallback (the underlying commands)

# install
npm ci
npm test
npm link

# update (from inside the checkout)
git pull --ff-only
npm ci
npm test
npm link

# uninstall
npm unlink -g dead-simple-dice

npm link exposes the local checkout as the global dsd command.

These scripts are for local git checkouts, not registry installs.


Use it

dsd <notation> [options]
echo <notation> | dsd

Common rolls

| Command | Result | |---|---| | dsd d20 | Roll one twenty-sided die | | dsd 4d6 | Roll four six-sided dice | | dsd d6 -n 4 | Roll four six-sided dice | | dsd d6 --number 4 | Roll four six-sided dice | | dsd d100 --number 2 | Roll two hundred-sided dice | | echo d20 \| dsd | Roll notation from stdin | | printf 'd20\n4d6\n' \| dsd | Roll several stdin lines |

Flags

| Flag | Meaning | |---|---| | -n <N> | Number of dice to roll | | --number <N> | Number of dice to roll | | -h, --help | Print help | | -V, --version | Print version |

Display presets

| Flag | Meaning | |---|---| | --plain | Force plain output; disables fancy/color/animation | | --ui | Static fancy TUI box | | --cyber | Fancy TUI + reveal animation + colored border | | --fast | Faster animation timing | | --slow | Slower animation timing | | --no-color | Disable animated border color | | --ascii | Force the ASCII box theme (no Unicode glyphs) | | --unicode | Force the Unicode box theme |


Dice notation

Supported notation is intentionally small.

| Notation | Meaning | |---|---| | d20 | One twenty-sided die | | 4d6 | Four six-sided dice | | d100 | One hundred-sided die | | d100 --number 2 | Two hundred-sided dice |

Rules:

  • Dice count must be positive.
  • Dice sides must be positive.
  • Count is capped at 100.
  • Sides are capped at 1,000,000.
  • The d is case-insensitive: 4D6 works.
  • Leading/trailing whitespace is trimmed.
  • Internal notation whitespace is rejected: d 20 is invalid.
  • -n / --number overrides a leading count: 4d6 -n 2 rolls two d6.
  • Repeated -n / --number uses the last value.

Not supported yet:

d%
advantage/disadvantage
exploding dice
keep/drop notation
full dice expressions

Output format

Default output is plain on purpose.

dsd d20
# 17

dsd 4d6
# 3 5 1 6

That means output stays useful in scripts:

roll="$(dsd d20)"
printf 'rolled: %s\n' "$roll"

No banners. No JSON by default. No color unless a display mode is explicitly enabled and stdout is a real terminal.


Display modes

Plain text is the default. Fancy output is opt-in.

| Command | Result | |---|---| | dsd d20 | Plain default | | dsd d20 --plain | Force plain output | | dsd d20 --ui | Static TUI box | | dsd d20 --cyber | TUI box + reveal animation + colored border | | dsd d20 --cyber --fast | Faster reveal | | dsd d20 --cyber --slow | Slower reveal | | dsd d20 --cyber --no-color | Animated TUI without colored border |

Fancy TUI

╭─ DSD // DEAD-SIMPLE-DICE ─╮
│ notation : 4d6            │
│ result   : 3 5 1 6        │
╰───────────────────────────╯

ASCII fallback:

+- DSD // DEAD-SIMPLE-DICE -+
| notation : 4d6            |
| result   : 3 5 1 6        |
+---------------------------+

Cyber reveal

The animation is not a loading bar. It is a left-to-right lock-in that runs through three phases — ROLLING (churning block/noise placeholders), LOCKING (real dice revealed one at a time), and RESULT (the full result, held for a couple of settle frames). For final 4d6 = 3 5 1 6:

ROLLING  ▒ ▓ █ ░
LOCKING  3 ▓ █ ░
LOCKING  3 5 █ ░
LOCKING  3 5 1 ░
RESULT   3 5 1 6

The dice are rolled once; the placeholder frames are purely visual and never re-roll. Normal frames are paced at a readable ~140ms each. --fast (~70ms) uses a shorter delay, and --slow (~220ms) a longer one.

With --cyber, the same reveal plays inside the box, the box gains a phase row, and the box border pulses through a restrained cyberpunk palette (cyan, violet, magenta, bright white) as the frames advance. Only the border is colored, the rolled numbers stay uncolored, and every colored run is reset, so color never leaks. The final frame always shows the real result. Use --no-color or DSD_COLOR=0 to keep the fancy animation but disable the colored border.

Advanced display flags

These are kept for compatibility:

dsd d20 --fancy        # alias for --ui
dsd d20 --pretty       # alias for --ui
dsd d20 --no-fancy     # force fancy box off
dsd d20 --no-pretty    # alias for --no-fancy
dsd d20 --animate      # plain reveal animation
dsd d20 --no-animate   # force animation off

--plain wins over display flags and display environment variables. If both --fast and --slow are passed, the last speed flag wins.

Environment toggles

Truthy values: 1, true, yes, on.

DSD_UI=1               # static fancy TUI
DSD_CYBER=1            # fancy + animation + color
DSD_COLOR=0            # disable animated border color
DSD_ASCII=1            # force the ASCII box theme
DSD_SPEED=fast         # fast, normal, or slow

Backward-compatible env vars:

DSD_FANCY_TUI=1        # static fancy TUI box
DSD_PRETTY=1           # alias for DSD_FANCY_TUI
DSD_ROLL_ANIMATION=1   # reveal animation

Falsy values: unset, 0, false, no, off. Unknown values are falsy for on/off toggles.

Terminal rendering safety

The rendering layer is built as a small cell-layout system with strict invariants:

  • Effects render only on a real terminal. Piped or redirected stdout is always plain text — no ANSI, color, box, or animation.
  • Each animation frame is a complete, measured frame. Every frame re-reads the terminal width, rebuilds the whole frame at that width, moves the cursor up the previous frame's height, clears downward, and writes the new frame, so a mid-animation resize never leaves a smear.
  • Every row is clipped and padded to one chosen width. The box width is decided once per frame from the title and the widest content row, clamped to the terminal columns. If content is too wide, the content is clipped with an ellipsis — the border is never pushed past the terminal.
  • Borders and text are separate concepts. Content is built and measured as plain "label : value" rows; borders are wrapped around them afterward.
  • Color is applied after layout. Visible-width math ignores ANSI escapes, so color can never affect alignment, and every colored segment self-resets.
  • Tiny terminals fall back to compact output (below ~28 columns) instead of drawing a broken box.
  • Unicode vs ASCII is chosen by --ascii / --unicode (or DSD_ASCII=1); a dumb terminal auto-selects ASCII. Non-TTY output stays plain regardless.

Piping

dsd is built to behave like a boring Unix utility.

echo d20 | dsd
printf 'd20\n4d6\nd100\n' | dsd

Behavior:

  • CLI args override stdin.
  • No args + piped stdin reads stdin.
  • Multiple stdin lines produce multiple output lines.
  • Blank stdin lines are ignored.
  • Empty input exits 1 with usage/help.
  • Normal terminal use should not hang waiting for input.

Non-TTY safety

Effects only render when stdout is a real terminal. Piped or redirected output is always plain text:

DSD_CYBER=1 dsd d20 | cat
# 17

No ANSI leaks. No animated noise frames in pipes. No color in redirected output.


Errors and exit codes

Invalid commands exit 1 and print the problem to stderr.

dsd d0
# dice sides must be at least 1, got 0

dsd 0d6
# dice count must be at least 1, got 0

dsd 4dd6
# invalid dice notation: "4dd6" (expected forms like d20 or 4d6)

dsd d6 -n
# missing value for -n (expected a positive integer)

dsd --badflag
# unknown flag: --badflag

| Code | Meaning | |---:|---| | 0 | Success | | 1 | Invalid input, invalid notation, or usage error |


Randomness

Normal CLI rolls use Node's built-in crypto.randomInt through the production roller.

What that means:

  • rolls are generated directly in 1..sides
  • there is no modulo-bias shortcut
  • there is no smoothing or balancing
  • there is no hidden reroll logic
  • there are still normal random clumps and bumps in real samples

crypto.randomInt is chosen over Math.random deliberately: it draws a uniform integer with no modulo bias, and its per-roll cost is negligible for an interactive CLI. The roller is injectable (rng(sides)), so tests stay deterministic without weakening the production path.

Manual randomness audit

Run a one-million-roll d20 audit:

npm run analyze:random

It prints count data and a chi-square statistic, then writes local reports:

| File | Purpose | |---|---| | reports/random-d20-1m.csv | Raw count data | | reports/random-d20-1m.svg | Browser-viewable bar chart |

Open the graph:

xdg-open reports/random-d20-1m.svg

This is a visual sanity check, not proof of perfect randomness. reports/ is gitignored.


Development

./install-dev
./status
./test
./audit

The developer wrappers keep the common workflows short:

| Command | Purpose | |---|---| | ./dev | Cozy project dashboard with version, branch, tests, and release readiness | | DSD_DEV=1 ./dev | Developer dashboard with extra release/remote internals | | ./doctor | Toolchain, permissions, workflow, release, and npm auth checks | | ./test | Existing npm test suite | | ./audit | Tests, release checks, pack dry run, workflow sanity, and shellcheck | | ./status | Branch, package version, npm/release/CI status, dirty files, pending tags | | ./sync | Fetch, rebase from upstream, and install dependencies | | ./sync --hard | Reset to GitHub source of truth, clean, submodules, reinstall | | ./release patch | Clean-tree release pipeline; also supports minor and major |

The deterministic test suite covers:

  • parser valid/invalid cases
  • validation bounds
  • CLI flags
  • missing flag values
  • unknown flags
  • stdin behavior
  • output formatting
  • display presets
  • non-TTY safety
  • install script structure
  • deterministic injected RNG behavior
  • clean stderr on failures

Layout

package.json
package-lock.json
src/
  index.js
  parser.js
  validate.js
  roll.js
  format.js
  env.js
  display.js
  color.js        # compat re-export of render/color.js
  tui.js          # compat re-export of render/{terminal,animation}.js
  cli.js
  render/
    index.js      # public render entrypoint
    ansi.js       # raw ANSI escape constants/helpers
    color.js      # SGR palette + border color (applied after layout)
    cell.js       # visible width, clip, pad, fit (ignores ANSI)
    layout.js     # rows/cells/borders -> measured box lines; themes
    model.js      # pure display model from a rolled result
    animation.js  # deterministic frame generation (pure)
    terminal.js   # the only writer: redraw loop, cursor, resize
scripts/
  lib.sh
  dev
  test
  release
  sync
  audit
  doctor
  status
  install-dev
  benchmark
  clean
  update
  install.sh
  update.sh
  uninstall.sh
  analyze-random.js
tests/
  parser.test.js
  validate.test.js
  roll.test.js
  format.test.js
  display.test.js
  color.test.js
  cell.test.js
  layout.test.js
  animation.test.js
  terminal.test.js
  install-script.test.js
  cli.test.js

Architecture boundary

parse -> validate -> roll -> build display model -> render

Rules:

  • parser does not roll dice
  • validator owns bounds
  • roller does not parse notation
  • formatter does not generate randomness
  • CLI owns argv/stdin/stdout/stderr/exit codes
  • tests can inject deterministic RNG / die rollers
  • rendering never parses, validates, rolls, or re-rolls; rolling knows nothing about ANSI, terminal width, frames, color, or layout (see src/render/)

Security posture

Dead-Simple-Dice is intentionally narrow.

Current expectations:

  • no network calls
  • no telemetry
  • no accounts
  • no config system
  • no shell execution from dice input
  • no arbitrary file reads from dice notation
  • no normal-roll file writes
  • no runtime dependencies
  • generated reports ignored by git

See SECURITY.md for the full policy.


Roadmap

| Done | Later | Maybe never | |---|---|---| | MVP CLI | npm package publication | full dice expression language | | d20, 4d6, d6 -n 4 | shell completions | exploding dice | | stdin support | man page | keep/drop rules | | help/version flags | d% shorthand | advantage/disadvantage shortcuts | | clear errors | --sum | interactive mode | | deterministic tests | --quiet | full-screen GUI | | crypto-backed rolls | --json | online rolling | | manual randomness audit | --seed | | | display presets | | | | install/uninstall scripts | | |


Project docs

| File | Purpose | |---|---| | CLAUDE.md | Implementation/spec notes for coding agents | | TODO.md | Checklist and future work | | SECURITY.md | Security policy | | CONTRIBUTING.md | Contribution rules | | LICENSE | MIT license |


AI-use disclosure

This project has been developed with assistance from AI tools, including ChatGPT Plus, Codex, and Claude Code.

Those tools can be wrong. Code, docs, tests, and project direction still need human review before they are trusted.

The maintainer understands why many programmers, artists, writers, and workers distrust AI tools. That distrust is not dismissed here.


Contributing

Keep patches small, testable, offline, and easy to review.

Before changing behavior:

npm ci
npm test

Useful smoke checks:

dsd --help
dsd --version
dsd d20
dsd 4d6
echo d20 | dsd

Read CONTRIBUTING.md before opening a patch.


License

MIT. See LICENSE.