Server-side rendered shell scripts delivered over HTTP. Shells make GET requests; the server generates and returns complete shell scripts that the shell executes locally. Supports nushell, PowerShell, and zsh.
Dependencies:
- shire (
@meop/shire) — shell abstraction library (cmd parsing, shell-specific syntax generation) - wut-config — config files (package definitions, dotfile mappings, scripts) — loaded via
WUT_CFG_DIRenv var
deno task check # type check
deno task format # apply formatting (modifies files)
deno task format:check # verify formatting without modifying (CI / pre-commit)
deno task lint # lint
deno task start # development mode with hot reload
deno task start:systemd # start via systemd
deno task stop:systemd # stop via systemd
deno task test # run tests (snapshot + syntax)
deno task test:update # regenerate snapshots after intentional changesdeno task format— apply formattingdeno task lint— fix errors, return to step 1 if anydeno task test— if snapshot tests fail due to intentional output changes:deno task test:update, then reviewgit diff src/cmd/__snapshots__/— Tier 2 syntax check is automatic; semantic correctness requires human review
deno outdated— check for available updatesdeno update— update lockfile within version constraintsdeno update --latest— update deno.json and lockfile to absolute latest
Deno formatting rules (deno.json):
- No semicolons
- Single quotes
- Trailing commas only on multiline
- Always use curly braces for
ifstatement bodies, with body on next line
Imports must be organized into 3 groups with a single empty line between each group, and sorted alphabetically by source within each group:
- Built-in modules (e.g.,
node:*) - External packages (e.g.,
@cross/*,@meop/shire,@std/*) - Local project files (e.g.,
./cfg.ts,../sh.ts)
Example:
import { readFileSync } from 'node:fs'
import { CmdBase } from '@meop/shire/cmd'
import { getCtx } from '@meop/shire/ctx'
import { assertEquals } from '@std/assert'
import { getCfgFileLoad } from './cfg.ts'
import { SETTINGS } from './stng.ts'Config is loaded from a single directory specified by cfg.dir in settings.toml, overrideable via the WUT_CFG_DIR
environment variable. The in-repo cfg/ directory contains minimal example configs used by tests.
Two distinct loading functions:
getCfgFileContent(parts)— raw file content. Used by the/cfgHTTP route for files fetched at runtime by nushell scripts (containerfiles, pod YAMLs).getCfgFileLoad(parts, {extension})— loads and parses a single config file (YAML/JSON). Used by commands that process config server-side (pack, file, script, virt).
All ops use AND semantics (every filter term must match; more terms = narrower). Each op resolves filters via one of two philosophies — WIDE (substring, act on all) or PINPOINT (exact-wins then first, act on one). See docs/COMMANDS.md for the per-op table and implementation.
Every op resolves data on the server, lets the client filter it by what is actually installed
(packManagerHere/virtManagerHere/fileBinHere/scriptHasCmd), summarises it as a table, and asks exactly once —
find included. The server never pre-renders a listing, because pre-rendered text has nowhere left to apply that
filter. See docs/OPS.md.
Cross-cutting:
- docs/OPS.md — the shape every command shares: server resolves, client filters, one prompt
- docs/COMMANDS.md — how filters resolve to targets (WIDE vs PINPOINT), per-op table
- docs/RULES.md — config file shapes:
file.yaml, pack group yaml,script.yamlgates - docs/NUSHELL.md — nu parsing quirks that have caused bugs
- docs/TESTS.md — test architecture, snapshots, adding cases
Per command:
- docs/PACK.md — groups, tiers, loose names, the plan
- docs/VIRT.md — instance layout, podman layers and builds, qemu variants, add vs run
- docs/FILE.md — keys as bin checks, and the one op that writes
- docs/SCRIPT.md — shell ownership and hops, client-side
has_cmd
The server primarily generates nushell scripts. pwsh/zsh shells are redirected to nushell equivalents for commands
with complex logic (pack, file, virt). See file.ts, virt.ts, pack.ts for redirect implementation.
script exec is the exception: each script runs in the shell it is written for, and hops between shells to fan out.
That ownership and hop protocol is in docs/SCRIPT.md.
src/sh/nu/ generates nushell; several parsing quirks (raw-string depth, bare words in assignments, [...]
list-vs-pipeline, http get --raw) have caused bugs there. See docs/NUSHELL.md before editing
nushell output.
See docs/TESTS.md for full details on test architecture, how to update snapshots, and how to add new test cases.
Some operations require local interactive testing and cannot be fully automated — see README.