esp — author Morrowind plugins as source code

esp is a Ruby toolchain for authoring Morrowind plugins without the Construction Set. You write records in JSON, Ruby, Python, JavaScript, or TypeScript; esp builds them to an .esp via tes3conv, manages scripts, translations, and dialogue, and lints against the real vanilla game data. Every operation renders as human text or structured JSON, so a CLI, an HTTP API, an MCP server, and an AI agent all drive the same pipeline.

Why — source is truth, the .esp is output

The Construction Set is a direct-manipulation binary editor: the .esp is the document. esp inverts that. You author intent as human-readable source and the .esp is a disposable build artifact — the relationship a compiler has with its object files. You never hand-edit the binary; you edit source and rebuild.

That one inversion buys the whole software-engineering toolchain: git diff / blame / branch / review, procedural generation, lint as a build step, CI, HTTP/MCP automation, and AI authoring. The cost is the loss of direct manipulation — spatial placement and visual browsing. ESPresso is the planned AI-first GUI built on this backend, where you describe intent, an agent writes diffable source, and you review the git diff.

Quickstart

Install once:

gem install esp-modkit    # puts `esp` on your PATH
# tes3conv is a required prerequisite, not on Homebrew: download a release
#   https://github.com/Greatness7/tes3conv/releases
# or:  cargo install --git https://github.com/Greatness7/tes3conv
esp doctor               # confirm Ruby, tes3conv, and the references index
esp refs unpack          # unpack vanilla ESMs to ~/.config/esp/references/ (one-time)
esp refs index           # build the SQLite index there (one-time)

Native extensions (sqlite3, zstd-ruby) compile on install, so a C toolchain is needed. To work from a checkout instead of the gem, clone the repo, bundle install, and put bin/esp on your PATH (the launcher pins this repo's Ruby + Gemfile).

Then per-project — anywhere on disk:

mkdir -p ~/morrowind/MyFirstMod && cd ~/morrowind/MyFirstMod
esp init                 # writes .esp/project.json + git init + mods/ + dist/
esp new MyMod            # scaffold mods/MyMod/
esp lint MyMod           # check references against vanilla data
esp build MyMod          # -> dist/MyMod.esp (in *this* project)
esp install MyMod                   # OpenMW: register via data= + content=
esp install MyMod --to-data-files   # Original engine: copy into Morrowind/Data Files/

esp walks up from the current directory to find the .esp/project.json marker, so commands work from any subdir of your project tree — no flags needed inside a project, --root PATH if you're scripting across several, or export ESP_PROJECT_ROOT=… to pin one per shell.

New here? Read the walkthrough — one plugin from empty to built, with the thesis shown in practice.

Three frontends, one pipeline

Every command has a --json mode whose payload is identical across all three frontends:

Frontend Command For
CLI esp <cmd> humans + scripts
HTTP esp serve ESPresso, local automation
MCP esp mcp serve (esp mcp install to wire up) AI clients (Claude Code / Desktop)

Command surface

doctor                               check prerequisites (Ruby, tes3conv, refs index)
init [NAME]                          new project (marker + git init + mods/ + dist/)
new MOD [--format rb|py|js|ts]       scaffold a mod
build [MOD] [--locale fr] [--all] [--install]   build -> dist/MOD[.locale].esp
watch MOD                            rebuild on change
lint MOD                             dangling-ref check (needs refs index)
extract-scripts MOD                  hoist inline script text to files
unpack PLUGIN [NAME]                 .esp -> mods/NAME/NAME.json
install MOD [--copy-to PATH | --to-data-files]    OpenMW + optional file copy
refs unpack | index | find Q         vanilla data: build + query
i18n check MOD                       missing/orphan locale keys
docs build | introspect              regenerate / dump docs
serve | mcp serve                    HTTP API / MCP server

Documentation

Regenerate the auto pages with bin/esp docs build; a pre-commit hook fails if they drift.

Development

bundle exec rake            # RuboCop + Minitest (must be green)
bundle exec rake admin      # local quality reports (coverage, lint, stats)