Install
Thunderstorm ships payloads for Claude Code, Codex, and Pi. tstorm install
puts one in a repository; each harness can also install it as a plugin of its
own.
tstorm install
Section titled “tstorm install”GOPRIVATE='github.com/stormlightlabs/*' go install github.com/stormlightlabs/thunderstorm/cmd/tstorm@maincd <your repository>tstorm installThe binary carries the workflow, so nothing has to be checked out anywhere.
Claude Code is the default target; --target codex and --target pi write
theirs, and --dir installs into a repository you are not standing in.
Four things happen, and --check reports all of them without writing:
- The payload is rendered into
.claude,.codex, or.pi. - The commands the workflow reserves for a person are merged into the
repository’s
permissions.deny. - The gate is registered in the same settings file, against
$CLAUDE_PROJECT_DIR/.claude/hooks/gate.sh. - A
.tstorm.tomlis written from what you named, and its documents tree is created.
Steps 2 and 3 are Claude Code’s. Codex reads an execution policy and Pi its
extension, so --target codex and --target pi skip them and say so.
Step 4 invents nothing. With no flags no config is written, because a board nobody named cannot be guessed:
tstorm install --board stormlightlabs/13 --documents docs/internaltstorm install --board stormlightlabs/13 --track Tropius # a shared board--no-settings and --no-config turn off a step whose file you keep yourself.
The install writes its own files and leaves every other file in the directory
alone, so your settings file, a hook of your own and a worktrees directory
beside the payload all survive it. A repository already using Claude Code
needs no flag for that. .tstorm-payload records which files the payload owns
and which version wrote them, and the next install replaces those and removes
the ones the source stopped producing.
What stops an install is a file the payload itself writes. It names every one of them and writes nothing:
.claude holds 1 path this payload also writes: skills/implement/SKILL.md
Run again with --replace to overwrite them, or move them aside.--replace overwrites exactly those paths, names each one it replaced, and
leaves the rest of the directory as it was. That is also how a repository
carrying an older hand-copied payload moves to a rendered one: every file the
new payload writes collides, the list says which, and --replace takes them
over.
tstorm uninstall
Section titled “tstorm uninstall”tstorm uninstall # every payload under the current directorytstorm uninstall --check # says what it would removeThe marker says what the payload owns, so uninstall removes those files and
leaves everything else, including a file of yours inside the payload
directory. A directory goes only once nothing is left in it. The deny rules
and the gate registrations come back out of the settings file, and the rest of
that file is untouched, so a hook you registered yourself on the same event
stays. --keep-settings leaves the rules in place for a repository that wants
to keep the decision without the loop.
.tstorm.toml is left where it is. Your board and documents tree outlive the
loop that read them.
tstorm update
Section titled “tstorm update”tstorm update # every payload under the current directorytstorm update --check # says what it would move, writes nothingtstorm update --dir ../other-repoUpdate finds each payload by its marker, names the version that wrote it and the one this binary carries, re-renders, and merges the settings again, so a command the workflow newly reserves reaches a repository that installed months ago. A repository working two harnesses carries two payloads, and both move. A payload installed before the marker carried a version reports an unknown one and updates anyway.
Your config is left alone. A repository’s board and documents tree are its own after the first install.
Update ends by saying when a newer tstorm has been released. That lookup is
cached for a day and gives up after three seconds, and --offline skips it.
tstorm update --self takes the new one: it downloads the release for your
machine, checks it against the checksums.txt published beside it, and
renames it over the binary you are running.
Claude Code as a plugin
Section titled “Claude Code as a plugin”claude plugin marketplace add stormlightlabs/thunderstormclaude plugin install thunderstorm@stormlightlabsThis keeps the payload in Claude Code’s own cache rather than in the
repository. /plugin marketplace add and /plugin install do the same from
inside a session. marketplace add also takes an HTTPS URL, an SSH URL, or a
local path. Thirteen skills, ten commands and four agents arrive; claude plugin details thunderstorm lists them and what they cost a session.
Enable it for everyone on a project
Section titled “Enable it for everyone on a project”Both commands take --scope project, which writes the declaration into the
project’s .claude/settings.json instead of the machine’s:
claude plugin marketplace add stormlightlabs/thunderstorm --scope projectclaude plugin install thunderstorm@stormlightlabs --scope project{ "extraKnownMarketplaces": { "stormlightlabs": { "source": { "source": "github", "repo": "stormlightlabs/thunderstorm" } } }, "enabledPlugins": { "thunderstorm@stormlightlabs": true }}Commit that file and everyone working in the project gets the workflow on their
next session. A directory source takes a path, and a relative one is read
from the project root, which is how this repository installs the payload it
renders.
The four denied commands are not in it, because no plugin mechanism carries a
permission. tstorm install --no-config merges them into the same file, or
add the permissions.deny block by hand. Either way, check the merge:
tstorm check policyIt reads the deny list from the workflow the binary carries and names every
reserved command .claude/settings.json does not deny. Run it in CI as well: a
command added to the workflow reaches the payload on the next update and the
repository’s settings never.
Enable it everywhere
Section titled “Enable it everywhere”codex plugin marketplace add stormlightlabs/thunderstormcodex plugin add thunderstorm@stormlightlabsStart a new session, review the plugin hook, and allow it before running the workflow. The hook blocks the four merge, review, and push command prefixes while the plugin is enabled.
Invoke a skill with its plugin-qualified name:
$thunderstorm:thunderstorm Run issue 123$thunderstorm:implement Work issue 456Enable it only in one project
Section titled “Enable it only in one project”Add this to the trusted project’s .codex/config.toml:
[marketplaces.stormlightlabs]source_type = "git"source = "https://github.com/stormlightlabs/thunderstorm.git"
[plugins."thunderstorm@stormlightlabs"]enabled = trueCodex loads the marketplace and enables the plugin only while it works in that project. Commit the file when every Codex user on the project should get the workflow.
Install it globally and choose projects
Section titled “Install it globally and choose projects”Register and install the plugin with the two global commands above. Then set
the user-level default in ~/.codex/config.toml:
[plugins."thunderstorm@stormlightlabs"]enabled = falseTurn it on in each trusted project’s .codex/config.toml:
[plugins."thunderstorm@stormlightlabs"]enabled = trueProject configuration overrides user configuration. Use /plugins to inspect
the installed plugin, or remove it with codex plugin remove thunderstorm@stormlightlabs.
Install tstorm, then install the repository as a Pi package for the current
user:
GOPRIVATE='github.com/stormlightlabs/*' go install github.com/stormlightlabs/thunderstorm/cmd/tstorm@mainpi install git:github.com/stormlightlabs/thunderstormFor one project, run this from the project root instead:
pi install git:github.com/stormlightlabs/thunderstorm -lThe local command writes .pi/settings.json. Pi asks the user to trust the
project before it loads the package.
Without a ref, Pi installs the default branch. To install a release instead, name its tag:
pi install git:github.com/stormlightlabs/thunderstorm@v0.1.0Pi keeps a tagged install at that tag through pi update. To move to a later
release, remove the package with pi remove and install the new tag.
The package loads the skills, prompts, and extension from payloads/pi. The
extension supplies the package path used by the scripts and blocks the four
merge and push command prefixes. tstorm dispatch starts each role in its own
Pi session. When Pi runs inside tmux or Zellij, dispatch opens a new window or
tab in that session so the operator can watch and control it.
Render from a checkout
Section titled “Render from a checkout”tstorm render builds a payload from a workflow directory rather than from the
one the binary carries, which is how thunderstorm builds the payloads it
publishes and how anyone editing the loop sees the result:
tstorm render --target claude --out payloads/claudetstorm render --target claude --out payloads/claude --check--source <dir> names the workflow tree, and install --source <dir> reads
one too, which is how an edit reaches a repository before it is committed.
Checks
Section titled “Checks”Skills call tstorm subcommands, so the binary has to be on PATH wherever
the loop runs. Pi needs it for role dispatch as well. Install it with:
GOPRIVATE='github.com/stormlightlabs/*' go install github.com/stormlightlabs/thunderstorm/cmd/tstorm@mainThe repository is private, so neither the module proxy nor the checksum
database can read it. GOPRIVATE sends Go to GitHub over git for both, and
git needs your GitHub credentials, which gh auth setup-git sets up. The
plugin and package installs below clone the repository the same way and need
the same credentials. @v0.1.0 takes the release instead of main.
A machine with no Go toolchain takes a binary from the
releases instead.
Each release carries linux and macOS builds for amd64 and arm64, with
checksums.txt beside them:
tar -xzf tstorm_0.1.0_linux_amd64.tar.gzinstall -m 755 tstorm ~/.local/bin/tstormtstorm versiontstorm version names the tag a build descends from and the commit under it,
v0.1.0+g1969674, so two builds of one tag are told apart. A release
names its tag alone. Homebrew is the other route planned, through a tap in
this organization, and it is the only other one.
Two of them run on their own once the payload is installed. A write is checked
for the frontmatter an issue cites a document by and for the writing tells the
catalogue lists, and both are reported into the session rather than refused. A
git commit is checked for its shape, and that one refuses: a subject that
will not read in git log --oneline cannot be fixed afterwards.
Hooks load when a session starts, so the session you install from gets none of this. Start a new one. A binary the hook cannot find lets everything through and says so once per machine, since it runs on every shell command a session makes.
Checks says what each check reads, what its exit code means, which harness runs which, and what to type where none of them runs.
From a checkout
Section titled “From a checkout”Working on the loop means building what you are editing:
git clone https://github.com/stormlightlabs/thunderstormcd thunderstormgo install ./cmd/tstorm # to $GOBIN, or ~/go/bintstorm versionA build from a checkout names the tag its tree descends from and the commit it
was built at, and appends .dirty where that tree had uncommitted changes.
go build -o ~/.local/bin/tstorm ./cmd/tstorm puts it somewhere else.
TSTORM_BIN names the binary for the hooks where it is on neither PATH nor
the payload’s bin/, which is how to run a build under test without
installing it.
What stays with the repository
Section titled “What stays with the repository”The loop is the same everywhere; what it runs against is not.
Configuration covers what a repository keeps for
itself. The gates AGENTS.md names are there, along with the documents tree,
the deny rules, the board, and which model carries which role.