- Rust 96.2%
- Shell 2.3%
- Nix 1.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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> |
||
| .cargo | ||
| .forgejo/workflows | ||
| ci | ||
| crates | ||
| fuzz | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| clippy.toml | ||
| config.example.toml | ||
| deny.toml | ||
| flake.lock | ||
| flake.nix | ||
| install.sh | ||
| osv-scanner.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
| SECURITY.md | ||
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-auditablewrites the crate graph into a.dep-v0section duringnix build([profile.release]keepsstrip = "none"so it survives); onesyftpass over the binary's runtime closure (closureInfoinflake.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), acargo fuzztarget on the same oracle, Miri (nightly), Kani scaffolded off. The workspace forbidsunsafeand denies clippy'sallgroup; the restriction lints andpedanticarewarnfor 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,
vrolls 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;ptoggles prepared;xforgets. The spell list is capped to what you can currently cast; pressffor 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;nstarts a new dated session note,tcycles density. - Meta — manage characters and campaigns;
Enteractivates the selected one,pplaces a character into a campaign,ncreates 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.