Keep store-to-supervisor wiring owned by EngineOps so state changes only schedule one reactive sync wave.
Legion Kommando & Kontrolle
Cloud provisioning for Tribes nodes, an Electron application with Svelte and TypeScript
Project Setup
Install dependencies
$ npm install
Run the app
$ npm run dev
$ VITE_LOCALE=de-DE npm run dev # Force UI language
Optional XState tracing
For local actor debugging, you can enable a dev-only XState trace log:
$ LEGION_TRACE_XSTATE=1 npm run dev
When enabled, the main process writes a JSONL trace file under the Electron user data logs
directory and prints the full path once on startup. This is local-only and intended for
development debugging of actor events, actions, and state transitions.
Run the CLI in development
Use the repo-local wrapper:
$ ./bin/legion-dev node list
$ ./bin/legion-dev provider configure hetzner --api-key-env HCLOUD_TOKEN
$ ./bin/legion-dev node create --name node-a --provider hetzner --region fsn1 --instance cax11
$ ./bin/legion-dev apply
Or via npm:
$ npm run cli -- node list
Quality checks
$ npm run precommit
$ npm run lint
$ npm run format:check
$ npm test
$ npm run check
Credentialed integration and E2E runs are documented in TESTING.md.
Architecture notes
For the current runtime layout, daemon security posture, GUI boundary, XState actors, and recovery loops, see docs/arch.md.
Refresh the pinned Guix base channel
When ../guix-fork changes, refresh the Guix pin in ../guix-tribes first:
$ (cd ../guix-tribes && ./scripts/update-base-channels-pin)
$ npm run generate:guix-base-channel
Use ../guix-tribes/scripts/update-base-channels-pin --official when switching
back to the mirrored official Guix channel.
Refresh the default kexec installer
The default kexec installer image is built and published by the Guix mirror
builder from the kexec-installer branch in guix-tribes. After changing the
installer definition or pinned Guix channels, move that branch to the desired
signed guix-tribes commit so the mirror builder can publish
tribes-1/guix-kexec-installer-<system>-<commit>.tar.gz.
After the mirror publishes the matching .sha256, refresh Legion's generated
pin:
$ npm run update:kexec-installer-pin
Regenerate OpenAPI clients
$ npm run update:hcloud-client
$ npm run update:serverspace-client
Refresh bundled data
npm run update:provider-catalogs reads provider credentials from the environment:
# Hetzner
export HCLOUD_TOKEN=...
# or
export HETZNER_API_TOKEN=...
# OVH
export OVH_APP_KEY=...
export OVH_APP_SECRET=...
export OVH_CONSUMER_KEY=...
# optional, defaults to ovh-eu
export OVH_ENDPOINT=ovh-eu
# Scaleway
export SCW_ACCESS_KEY=...
export SCW_SECRET_KEY=...
export SCW_DEFAULT_PROJECT_ID=...
# optional, defaults to fr-par-1
export SCW_DEFAULT_ZONE=fr-par-1
$ npm run update:provider-catalogs
When you enter the devenv shell, pre-commit hooks are generated from devenv.nix and run the same npm-based lint and format checks before commit.
Build installers
# For windows
$ npm run build:win
# For macOS
$ npm run build:mac
# For Linux
$ npm run build:linux