A LAN host for a tabletop RPG table. The GM's PC runs one binary; players
join from phones on the same network by address or QR code. All data stays
on the host in SQLite. GMs manage campaigns, combat, NPCs and the
compendium; players manage their characters; everyone shares real-time
collaborative notes. See CLAUDE.md for the architecture, rules and
conventions.
My first project made with claude
- Download the archive for your system from the Releases page and unpack it into its own folder.
- Copy
.env.exampleto.envin that folder and setADMIN_USERNAMEandADMIN_PASSWORD(the server refuses to start without them). - Run
gm-session-manager(gm-session-manager.exeon Windows) from that folder..envanddata/are looked up in the current directory. - Open
https://localhost:8080on the host PC. Localhost is always the GM. The page shows the join address and a QR code for players.
The host uses a self-signed certificate (in DATA_DIR/tls, renewed when a
network address changes or it nears expiry). Each browser shows a warning
once per certificate: choose "Advanced" and continue. The GM page shows the
certificate's SHA-256 fingerprint if you want to compare it with what the
browser shows.
The first time the host listens, Windows asks whether to allow
gm-session-manager.exe on networks. Allow Private networks (your home
Wi-Fi). If you dismissed the prompt, players cannot connect: open
"Windows Defender Firewall → Allow an app through firewall" and tick
Private for gm-session-manager, or run once as administrator:
netsh advfirewall firewall add rule name="gm-session-manager" dir=in action=allow protocol=TCP localport=8080 profile=privatego run builds a new temporary executable each time, so Windows may ask
again; a built binary keeps its permission.
Releases ship for Windows and Linux on x86-64. On macOS or ARM, build from
source (see Development): ./build.sh builds for the
machine it runs on.
Log in at /admin/login with the .env credentials. A device holds one
role: to switch between player and GM, log out and log in again.
Settings come from .env in the working directory; real environment
variables override it.
| Variable | Default | Notes |
|---|---|---|
ADMIN_USERNAME |
— | required |
ADMIN_PASSWORD |
— | required |
PORT |
8080 |
HTTPS port |
DATA_DIR |
data |
everything the app stores |
DATA_DIR holds the database (gm-session-manager.db), the certificate
(tls/), uploaded files (attachments/) and compendium sources
(compendium/). Back up this folder to keep your campaigns; stop the
server first, or copy the -wal and -shm files with the database.
The importer reads data folders from DATA_DIR/compendium/<source>/. For
the 2024 SRD, download the JSON files of
open5e-api data/v2/wizards-of-the-coast/srd-2024
into data/compendium/srd-2024/. They are imported at start-up and from the
Compendium page ("Import now"); unchanged folders are skipped. The SRD 5.2
is licensed CC-BY-4.0 and every entry shows its attribution.
Homebrew is written in the Compendium page and is shared by all campaigns.
- Go at the version in
go.mod. That is all you need to build and run: generated code and built assets are committed, except the browser libraries and the component script bundle: rungo run ./cmd/vendorjsandgo tool shadcn-templ bundleonce after cloning. - Only for changing sources:
- sqlc on PATH (migrations and queries),
- the Tailwind standalone CLI
on PATH as
tailwindcss(templates and classes), - golangci-lint.
templandshadcn-templare Go tool dependencies (go tool …), no install needed.
- No node or npm. Browser libraries are vendored by
cmd/vendorjs.
cp .env.example .env # then set ADMIN_USERNAME and ADMIN_PASSWORD
./run.sh # or: go run ./cmd/gm-session-manager| Change | Command |
|---|---|
db/migrations, db/queries |
go generate ./db (sqlc) |
*.templ, Tailwind classes |
go generate ./web (script bundle + templ + Tailwind) |
| shadcn-templ components | go tool shadcn-templ add <name>, then go generate ./web |
| browser JS versions | go run ./cmd/vendorjs -check lists newer releases; edit cmd/vendorjs/main.go, then go run ./cmd/vendorjs |
Then go build ./..., go test ./... and golangci-lint run.
GSM_COMPENDIUM_DIR=data/compendium go test ./compendium -run TestRealSource -v
checks the importer against a real data download.
Claude Code uses graphify to
query the codebase (see CLAUDE.md). The graph is committed
(graphify-out/graph.json, GRAPH_REPORT.md, graph.html); the rest of
graphify-out/ is a local cache. Optional; not needed to build or run.
-
Install uv, then graphify at the version in
.claude/skills/graphify/.graphify_version:uv tool install graphifyy==<version> # e.g. graphifyy==0.9.95
-
After changing code, refresh the graph and commit it with your change (AST only, no API cost):
graphify update . -
For a full rebuild (including the Markdown docs), run
/graphify .in Claude Code.
To update graphify:
-
Upgrade the tool:
uv tool upgrade graphifyy. -
Refresh the project's skill,
CLAUDE.mdsection and hooks:graphify claude install --project
-
Review
git diff: it rewrites.claude/skills/graphify/(including.graphify_version), the## graphifysection ofCLAUDE.mdand the hooks in.claude/settings.json. A locally editedSKILL.mdis kept asSKILL.md.bak; delete it once reviewed. -
Rebuild the graph with the new version (
graphify update .) and commit everything together.
See the graphify README for everything else (queries, hooks, exports).
build.sh is the one build process, for you and CI. It fetches the vendored
JS, builds the shadcn-templ script bundle (both are gitignored), runs the
tests and stamps the version (git describe, or $VERSION) into the binary:
./build.sh # this machine → dist/gm-session-manager[.exe]
./build.sh release # every target → dist/ archives + SHA256SUMS (needs zip)Pushing a v* tag runs .github/workflows/release.yml: it runs
golangci-lint, then build.sh release (Windows and Linux x86-64 binaries,
no CGO, so every target builds from one runner, each packed with
README.md and .env.example), and publishes a GitHub release with
generated notes. .env and data/ are git-ignored and never shipped.
-
If sources changed, regenerate (
go generate ./db ./web) and commit. -
Check:
go build ./...,go test ./...,golangci-lint run. -
Tag and push (from a clean, pushed
main; it refuses a version that isn't newer than the latest tag unless you add--force):./release.sh 0.2.0
-
Watch the run on the repository's Actions tab (
gh run watch); the release appears under Releases when it finishes.
Build and upload it yourself:
VERSION=v0.1.0 ./build.sh release
gh release create v0.1.0 dist/* --generate-notes