No description
  • Rust 96.2%
  • Shell 2.3%
  • Nix 1.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Agent c23a48d384
Some checks failed
ci / gate (push) Successful in 30s
ci / deps (push) Successful in 16m52s
ci / static (push) Successful in 16m39s
ci / build (push) Failing after 18m21s
fix(engine): reject 0d6 instead of silently rolling a single die
Port the upstream character-sheet-template fix (889e104): the vendored
copy here still normalized Tok::Dice(0, _) via count.max(1), so a
zero-dice expression like 0d6 silently rolled one die instead of
reporting the typo. Bail with the same message the template uses and
add 0d6 to error_paths regression coverage.

Assisted-by: Agent (claude) <ai@blobfish.icu>
2026-07-23 09:29:15 -04:00
.cargo ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
.forgejo/workflows ci(pipeline): allow two transitive ratatui advisories in osv-scanner 2026-07-22 00:52:24 -04:00
ci ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
crates fix(engine): reject 0d6 instead of silently rolling a single die 2026-07-23 09:29:15 -04:00
fuzz feat(fuzz): cover slugify_checked contract in the slug target 2026-07-23 07:14:04 -04:00
.gitignore ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
Cargo.lock refactor(engine): harden atomic write, forward-compat HealthStatus, drop cross-repo refs 2026-07-23 07:13:58 -04:00
Cargo.toml refactor(engine): vendor sheet-engine and route slug through it 2026-07-20 17:37:01 -04:00
clippy.toml ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
config.example.toml docs(workspace): document chord layer in config example; simplify README 2026-07-23 07:15:36 -04:00
deny.toml ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
flake.lock build(nix): commit flake.lock for reproducible builds 2026-07-23 09:24:18 -04:00
flake.nix ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
install.sh chore(scripts): add cargo install helper script 2026-07-12 09:17:54 -04:00
osv-scanner.toml ci(pipeline): allow two transitive ratatui advisories in osv-scanner 2026-07-22 00:52:24 -04:00
README.md docs(workspace): document chord layer in config example; simplify README 2026-07-23 07:15:36 -04:00
rust-toolchain.toml ci(pipeline): add Nix SBOM build and Forgejo security workflows 2026-07-20 17:37:17 -04:00
rustfmt.toml build(workspace): add cargo workspace manifest and lockfile 2026-07-12 09:17:25 -04:00
SECURITY.md docs(security): add SECURITY.md and README supply-chain section 2026-07-20 17:37:17 -04:00

dnd5e

A Cargo workspace of tools for running a Dungeons & Dragons 5th-edition character.

crates/engine sheet-engine: vendored primitives (dice, roll, search, slug)
crates/core   shared library: model, dice, XDG store, config, SRD creation data
crates/tui    dnd5e-tui — full-screen terminal character manager
crates/cli    dnd5e     — command-line manager over the character set

Storage

Characters live in an XDG data directory (default $XDG_DATA_HOME/dnd5e/, i.e. ~/.local/share/dnd5e/):

characters/<slug>.yaml    one sheet per character
ACTIVE_CHARACTER          symlink -> the active character's sheet
ACTIVE_CAMPAIGN           symlink -> the active campaign directory
campaign/<name>/notes/<slug>/   per-character notes within a campaign

Configuration is read from $XDG_CONFIG_HOME/dnd5e/config.toml (see config.example.toml), including an optional campaign_dir that points the TUI's Notes tab at this repo's notes/<slug>/.

Install

cd dnd5e
./install.sh           # cargo install both binaries, then `dnd5e init`

To build without installing:

cargo build            # or: cargo build --release
cargo test

Security & supply chain

The release artefact is built by Nix, its SBOM is generated from the build rather than scanned off a finished binary, and provenance is signed and independently checkable. This reaches SLSA Build L2 in form (signed provenance from a hosted runner) but not L3 (the cosign key is readable by the release job); SECURITY.md states the gap and is the process half of CRA compliance.

Out of the box:

  • One-pass CycloneDX 1.6 SBOM. cargo-auditable writes the crate graph into a .dep-v0 section during nix build ([profile.release] keeps strip = "none" so it survives); one syft pass over the binary's runtime closure (closureInfo in flake.nix) then catalogs both the native store packages and the Rust crates — one tool, both halves.
  • Code scanning every PR (.forgejo/workflows/ci.yml): cargo-audit, cargo-deny, osv-scanner, cargo-machete, cargo-geiger, rustfmt, clippy, opengrep.
  • Dynamic + static analysis: proptest properties over the engine primitives (crates/engine/src/props.rs), a cargo fuzz target on the same oracle, Miri (nightly), Kani scaffolded off. The workspace forbids unsafe and denies clippy's all group; the restriction lints and pedantic are warn for now.
  • Reproducibility enforced at release (nix build --rebuild), signed tags required, and every SBOM pushed to Dependency-Track.
cargo ci                          # fmt, clippy, test, deny, audit
nix build .#sbom && jq . result   # the CycloneDX SBOM (native + Rust)
nix build .#cli                   # the auditable release binary

CI runs on a Forgejo runner labelled default: each job runs in a Node container and installs Lix (ci/install-nix.sh). nix build and the release pipeline need Cargo.lock and flake.lock committed.

CLI — dnd5e

command what it does
init scaffold the data + config directories
list list stored characters (* marks active)
show [slug] show a full character sheet (default: active)
use <slug> set the active character
import <path> import a sheet file and activate it
classes / races / backgrounds list creatable options
feats [--category <cat>] [--version <ed>] browse the feat catalog
roll <expr> dice roller, e.g. roll "d20 + 5", roll 4d6kh3
new [--class --race --subrace --background --name --method --version --level] interactive creation
new --auto ... roll/assign a whole character in one shot
new --level <1-20> ... build directly at any level (subclass, HP, slots, ASIs)
levelup [slug] [--class <id>] advance a level (subclass, HP, slots, ASI)
rest <long|short> [slug] restore HP, hit dice, and spell slots
convert [slug] --to <2014|2024> port a character between editions
campaign {list,new,use} manage note campaigns

Editions (2014 / 2024)

Every character carries a rules_version tag (2014 or 2024, default 2024). dnd5e new --version 2014 builds under the 2014 rules — ability score bonuses come from your species, subclasses are chosen at their 2014 levels (Cleric/ Sorcerer/Warlock at 1, Druid/Wizard at 2), backgrounds grant a roleplay feature instead of an Origin feat, and there is no weapon mastery. The default 2024 path takes ability bonuses from the background (plus an Origin feat), chooses all subclasses at level 3, and adds weapon mastery. Set rules_version in config.toml to change the default.

dnd5e convert <slug> --to 2014 (or --to 2024) flips the tag, adds or clears weapon mastery, keeps your ability scores, and prints a checklist of the manual adjustments each edition implies (ability-score source, feats, subclass timing).

Creation

dnd5e new walks class, race/subrace, background, ability scores (roll, standard array, or point buy), class skills, and subclass, then assembles a level-1 sheet with derived AC, attacks, hit points, and (for casters) spell slots, save DC, and a starting spell list. With no TTY every prompt takes its default, so dnd5e new --auto --class cleric builds a full character.

--level <1-20> builds directly at a higher level instead of level 1. Under --auto every level's choices are resolved automatically — the first subclass is taken at its subclass level, each Ability Score Improvement pours +2 into the class's primary abilities (never past 20), and hit points, hit dice, spell slots, features, and known/cantrip spells all scale up. So dnd5e new --auto --class wizard --level 8 yields a complete 8th-level Evoker. Interactively, dnd5e new --level 8 prompts for the subclass and each ASI as those levels are reached (equivalent to running dnd5e levelup up to that level).

Feats

crates/core/data/feats.yaml holds the feat catalogue — 2024 Origin, General, Fighting Style, and Epic Boon feats plus the 2014-only PHB/racial feats, edition-tagged. Browse them with dnd5e feats (filter with --category origin|general|fighting_style|epic_boon or --version 2014|2024).

A background still grants its Origin feat at level 1. At each of a class's ASI levels, dnd5e levelup and interactive dnd5e new --level offer a choice between an Ability Score Improvement and a feat — only feats the character qualifies for under its edition (level, ability, proficiency, or spellcasting prerequisites) are listed, and half-feats prompt for which ability to raise. --auto always takes the ASI, so feats never interrupt a one-shot build or normal play. The tools apply a feat's mechanical parts automatically (ability increases, granted proficiencies/expertise, speed, initiative, hit points, and limited-use resources); anything finer lives in the feat's feature text for you to track. Feats a character holds are listed on dnd5e show.

The dice roller understands NdM, keep clauses (4d6kh3, 2d20kl1), constants, +/-, and a x/* multiplier.

TUI — dnd5e-tui

# First run: pass a sheet to import into the store and open it.
cargo run -p dnd5e-tui -- ../characters/aria-nightbreeze.yaml
# Later: no argument opens the active character.
cargo run -p dnd5e-tui

Five tabs, cycled with Tab:

  • Sheet — a 2x2 grid: Abilities (Enter rolls a check, v rolls the save), Skills (Enter rolls a check), Combat (HP row adjusts with +/-; Enter on a weapon rolls attack and damage, natural 20 doubles the damage dice; the weapon's 2024 mastery is shown), and Status — the in-play toggles: Heroic Inspiration (Enter toggles), Temp HP and Exhaustion (+/-), Death Saves (successes/failures, Enter ticks one, +/- adjust), and the feature list (Enter shows detail).
  • Spells — a browsable grimoire: your Prepared/Known list and the class spell list to learn from (switch with h/l), a detail pane, and a slot bar. Enter casts (expends a slot) or learns; +/- restore/expend a slot of the selected spell's level; p toggles prepared; x forgets. The spell list is capped to what you can currently cast; press f for reference view to browse the full class list at every level (e.g. to add higher spells to a wizard's book).
  • Actions — the PHB default actions (Attack, Dash, Disengage, Dodge, Help, Hide, Influence, Magic, Ready, Search, Study, Utilize). Enter rolls the relevant check (Hide → Stealth, Search → Perception, …) or shows a summary.
  • Notes — a git log-style chronology wired to $EDITOR; n starts a new dated session note, t cycles density.
  • Meta — manage characters and campaigns; Enter activates the selected one, p places a character into a campaign, n creates a campaign.

The roll popup takes a advantage, d disadvantage, r reroll. R long rest, H short rest, s saves the sheet.

? shows a full keybinding help panel; g opens the config file in $EDITOR and reloads it on save. Colours and every keybinding are configurable in config.toml — see config.example.toml.

Licence and rulebook material

The code and tooling here are MIT-licensed. This is an unofficial, fan-made helper targeting the 2024 ruleset. The creation data under crates/core/data/ is paraphrased from the 2024 Player's Handbook / SRD (Wizards of the Coast, CC-BY-4.0) and reconstructed from the published rules where needed; no rulebook scans or verbatim text are included. Some feature text is approximated — corrections welcome.