- Go 94.5%
- Just 2.9%
- Shell 2.6%
go get recorded the local toolchain's patch version; the suite pins major.minor so the golang:1.26 CI image qualifies regardless of patch. |
||
|---|---|---|
| .just | ||
| .woodpecker | ||
| internal | ||
| scripts | ||
| .gitignore | ||
| CHANGELOG.md | ||
| config.example.toml | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| main.go | ||
| README.md | ||
moor
Switch the shell between development domains. A domain is a context under the
workspace — client work, company work, private tools — and m amedia/foo
jumps to a checkout while activating everything that context implies: the mise
toolchain declared in the domain root, per-domain language caches, profile env
vars, and the sage ssh identity. Switching is exclusive and eval-based, like
sage load and skiff switch: moor prints a snippet, the m() wrapper
applies it, and the next switch reverts it exactly. No per-cd hook, no nix, no
direnv.
moor deliberately does not manage what can be dispatched statically: git
identity belongs in ~/.gitconfig via includeIf "gitdir:…", ssh keys in
~/.ssh/config via per-host IdentityAgent pointing at sage's sockets.
Tool provisioning is delegated to mise — each domain
root carries a plain mise.toml, and moor uses mise purely as a provisioner
and env printer (no mise shell activation is installed).
Install
Requires Go 1.26+ and just.
just install # builds and copies to /usr/local/bin/
Then in ~/.zshrc, after compinit:
eval "$(moor hook zsh)"
Usage
m amedia/foo # cd ~/dev/amedia/foo + activate the amedia domain
m vardevs # cd to the domain root, activate vardevs
m . # re-sync context to the current directory
m # back to the workspace root, context ejected
m forks/thing # plain cd — not a domain, context left alone
moor status # active domain and the registry (also bare `moor`)
moor sync --all # mise install + cache dirs for every domain
moor switch amedia # the raw snippet m() evals (also: eject, cd, hook)
Config
The registry is explicit TOML at ~/.config/moor/config.toml — see
config.example.toml:
workspace = "~/dev"
[domains.amedia]
sage = "amedia" # sage identity to load on switch
claudia = "amedia" # claudia profile to load on switch
langs = ["go", "node", "python", "maven"] # per-domain caches under <root>/.moor
hooks = ["cat .envrc.local"] # exports captured from each command
paths = ["bin"] # extra PATH entries (root-relative)
[domains.amedia.env]
K8S_OBJECTS_DIR = "/home/varl/dev/amedia/tools/k8s-objects"
A hook is any command that prints export K=V statements (sage-style); moor
captures them into the switch — PATH additions diffed against the base, other
vars tracked and reverted like everything else.
Tools live in the domain root's mise.toml, mise-native:
[tools]
go = "1.26"
kubectl = "latest"
"ubi:gini/dexter" = "0.5.2"
Subprojects go one tier below the domain: a mise.toml inside a checkout
(e.g. [env] _.file = ".env" to load that dir's dotenv) joins the context
when you m into it — moor captures mise env at the directory you land in,
and mise merges every config on the way up. New config files need a one-time
mise trust; an untrusted config anywhere in the chain makes mise refuse the
whole capture (moor degrades to a warning).
A mise.toml at the workspace root is the tier above: mise merges parent-dir
configs, so its tools reach every domain under the workspace (moor applies it
to config-less domains too) without touching the bare system. Machine-level
packages stay with the system package manager; only the two-plus-tier split
between "all domains" and "this domain" lives in mise.
How a switch works
moor switch <domain> prints a zsh snippet that:
- unsets every var the previous switch exported (tracked in
MOOR_VARS), - rebuilds
PATHas the domain's prepends over the pre-moor base (MOOR_BASE_PATH, recorded on the first switch in a shell), - exports the mise env for the domain root, the language-cache vars
(
GOPATH,NPM_CONFIG_PREFIX, …),MANPATHand zshfpathentries probed from the tool paths, and the domain's ownenvtable, - passes sage's own
export SSH_AUTH_SOCK…line through verbatim.
State lives only in the shell's environment (MOOR_DOMAIN, MOOR_VARS,
MOOR_BASE_PATH, MOOR_FPATH), so a switch is a pure function of the
registry plus the previous switch — nothing to get stale, nothing layered.
Migrating a domain off nix
- Write the root's
mise.tomlfrom the oldshell.nixbuildInputs. moor sync <domain>, then keep the caches:mv .nix-go .moor/go,mv .nix-npm .moor/npm,mv .nix-node .moor/node, and so on.- Delete
shell.nixand.envrc; drop the direnv wiring from~/.zshrconce no domain uses it.