launch-scaffolder is a single Rust binary that generates, installs, and
maintains cross-platform desktop launcher scripts for any hyperpolymath
project (or any other project that adopts the hyperpolymath launcher
standard). It turns launcher scripts into generated artefacts, not
hand-edited files.
Inputs:
-
launcher-standard.a2ml— a declarative description of what a compliant launcher looks like (modes, file paths, permissions, integrity requirements, per-platform behaviour). The canonical copy lives in thestandardsmonorepo at/var/mnt/eclipse/repos/standards/launcher/launcher-standard.a2ml. At runtime the loader resolves in this order:-
--standard <file>CLI flag -
$LAUNCH_SCAFFOLDER_STANDARDenvironment variable -
The canonical path above
-
A baked-in copy compiled into the binary at build time (fallback only)
-
<app>.launcher.a2ml— per-app config (name, display, URL, command, repo, icon, and an optional[exceptions]block).
-
-
Output:
-
<app>-launcher.sh— a fully spec-compliant cross-platform launcher (Linux / macOS / Windows-via-Git-Bash).
Without this tool, every hyperpolymath project maintains its own
hand-written ~600-line bash launcher script that has to be kept in sync
with the current launcher standard by hand. In practice, drift is
guaranteed. The launcher standard evolves; the launchers don’t. Fixing a
spec issue means editing 11+ files and hoping you caught them all.
With this tool:
-
The standard is a single file. Changing it is a one-line edit.
-
Every launcher is regenerated from the same standard + its own config.
-
Bulk realignment after a spec change is one command:
launch-scaffolder realign *.launcher.a2ml. -
Launchers become reproducible, auditable artefacts — not hand-edited drift.
-
"Not wasting tokens in future" — future AI sessions read one spec file, not 11 near-identical launcher scripts.
All four working subcommands, in the order you’re likely to use them:
# ─── mint ──────────────────────────────────────────────────────────────────
# Generate a launcher script from a config. Writes
# <config-parent>/<app-name>-launcher.sh by default.
launch-scaffolder mint examples/stapeln.launcher.fixture.a2ml
launch-scaffolder mint /path/to/burble.launcher.a2ml -o /tmp/out.sh
launch-scaffolder mint /path/to/burble.launcher.a2ml --stdout
# ─── realign ───────────────────────────────────────────────────────────────
# Bulk re-mint: walk the estate (default = /var/mnt/eclipse/repos) and
# re-render every live `<app>.launcher.a2ml` against the current standard
# + template. Idempotent — unchanged scripts stay unchanged.
launch-scaffolder realign # walk estate root
launch-scaffolder realign --search-root ~/my-projects # narrow walk
launch-scaffolder realign /path/to/one.launcher.a2ml # explicit configs
launch-scaffolder realign --dry-run # preview only
launch-scaffolder realign --check # CI mode: exit 1 on any diff
launch-scaffolder realign --keep-going # don't stop on errors
# ─── provision ─────────────────────────────────────────────────────────────
# Install (--integ) or uninstall (--disinteg) a launcher's desktop entry,
# icon, and launcher binary on the current system. Writes .desktop files
# directly from the Rust binary rather than going via the generated shell
# script. Bulk runs (--all) prompt before touching $HOME.
launch-scaffolder provision --integ /path/to/burble.launcher.a2ml
launch-scaffolder provision --disinteg /path/to/burble.launcher.a2ml
launch-scaffolder provision --integ --all # everything in the estate
launch-scaffolder provision --integ --all --no-confirm --force
launch-scaffolder provision --integ --all --dry-run # preview only
# ─── config ────────────────────────────────────────────────────────────────
# Inspect or edit the `@a2ml-metadata` block embedded at the top of any
# generated launcher script.
launch-scaffolder config get ./stapeln-launcher.sh version
launch-scaffolder config get ./stapeln-launcher.sh standards-compliance
launch-scaffolder config validate ./stapeln-launcher.sh
launch-scaffolder config set ./stapeln-launcher.sh version 0.2.0
# NOTE: `config set` rewrites the generated script in place and warns
# that `realign` will overwrite the change. The durable fix is to edit
# the source <app>.launcher.a2ml and re-mint.Generated <app>-launcher.sh scripts carry their own --integ /
--disinteg arms (the original, pure-bash implementation). They now
also embed the absolute path of their source config as CONFIG_FILE=…
and, on invocation, fast-path back to
launch-scaffolder provision --integ "$CONFIG_FILE" when the binary is
on PATH. If the binary isn’t present, the in-script shell fallback
runs instead. This means: the Rust binary is authoritative when
available, without making itself a hard dependency for integrated
launchers.
launch-scaffolder/
├── Cargo.toml # Workspace root
├── crates/
│ ├── launcher-common/ # Shared library — all real logic
│ │ └── src/
│ │ ├── lib.rs # Public API
│ │ ├── standard.rs # Parse launcher-standard.a2ml
│ │ │ # + LauncherStandard::resolve (shared
│ │ │ # 3-step precedence: flag → canonical
│ │ │ # → baked fallback)
│ │ ├── config.rs # Parse <app>.launcher.a2ml
│ │ ├── template.rs # Render via Tera (embeds CONFIG_FILE)
│ │ ├── discovery.rs # Walk + prune + fixture-suffix filter
│ │ │ # (shared by realign + provision)
│ │ ├── integration.rs # Native Rust .desktop writer,
│ │ │ # icon/launcher install, gio,
│ │ │ # update-desktop-database
│ │ ├── metadata_block.rs # Parser + in-place rewriter for the
│ │ │ # embedded @a2ml-metadata block
│ │ ├── platform.rs # Linux/macOS/Windows dispatch (stub)
│ │ ├── integrity.rs # SHA-256 manifests (stub)
│ │ └── exceptions.rs # Standard + config + exception merge (stub)
│ └── launcher/ # Thin CLI binary
│ └── src/
│ ├── main.rs # clap dispatch
│ ├── cmd_mint.rs # ✓ working
│ ├── cmd_realign.rs # ✓ working
│ ├── cmd_provision.rs # ✓ working (native Rust; option b)
│ ├── cmd_config.rs # ✓ working
│ └── cmd_standard.rs # stub
├── standards/
│ └── launcher-standard.a2ml # Canonical standard (baked into binary)
├── templates/
│ └── launcher.sh.tera # Bash launcher template rendered by Tera
├── examples/
│ ├── README.md # Fixture-vs-live naming convention
│ └── stapeln.launcher.fixture.a2ml # Worked example (fixture suffix!)
├── docs/
│ ├── launcher-exceptions-2026-04-10.md
│ ├── compliance-audit-2026-04-10.md
│ ├── branch-protection-remediation-2026-04-10.md
│ └── ruleset-audit-2026-04-10/ # Audit artefacts (read-only record)
└── tests/
└── regression/ # Golden-file regression fixtures (planned)To distinguish test fixtures from live, estate-owned launcher configs, file-name suffixes carry the distinction — directory names do not:
| Purpose | File-name suffix | Picked up by estate walks? |
|---|---|---|
Live per-app config |
|
Yes |
Fixture / worked example |
|
No |
The discovery code in launch-scaffolder-common::discovery::is_live_config
enforces this rule. Fixture files can live anywhere — including inside
a consumer repo’s examples/ directory — without being swept up by
realign or provision --all. See examples/README.md for the full
contributor-facing version of the rule.
Per the hyperpolymath language policy (see standards/rhodium-standard-repositories/spec/LANGUAGE-POLICY.adoc):
-
Rust is the preferred language for CLI tools — zero-dep binary, fast cold start, strong types, excellent ecosystem (clap, tera, sha2, anyhow).
-
"Rust" always means "Rust with SPARK integration as the default stance" — this tool is Rust-primary now, with SPARK/Ada hooks planned for the correctness-critical
integrity.rspath (called via Zig FFI per the hyperpolymath ABI/FFI standard).
-
A2ML is the hyperpolymath standard format for machine-readable config and metadata, and every launcher already carries an A2ML metadata block in its header. Using A2ML end-to-end means a launcher script can be parsed by this tool to extract its config and re-minted.
-
v0.1 uses TOML as the concrete syntax (A2ML is "TOML-like" per the standards
.claude/CLAUDE.md). v0.2 switches to proper A2ML once thea2ml-rsparser reaches feature parity.
Alpha, ~65% complete. Four of five subcommands fully wired end-to-end;
one remains a stub (standard). Last updated 2026-04-10 (phase
phase-4-config-inspector).
Implemented:
-
✓ Cargo workspace layout (two crates:
launch-scaffolder-common+launch-scaffolder) -
✓
standardmodule — parseslauncher-standard.a2ml;LauncherStandard::resolvehosts the shared 3-step precedence (flag → canonical → baked) -
✓
configmodule — parses<app>.launcher.a2ml(TOML) with runtime-kind validation -
✓
templatemodule — Tera renderer with full context; embedsCONFIG_FILEfor in-script delegation -
✓
discoverymodule — shared walk + prune + fixture-suffix filter -
✓
integrationmodule — native Rust.desktopwriter, icon/launcher install, best-effortgio+update-desktop-database(Linux only; macOS/Windows returnIntegError::UnsupportedPlatform) -
✓
metadata_blockmodule — hand-rolled parser and in-place rewriter for the embedded@a2ml-metadatablock -
✓
templates/launcher.sh.tera— parameterised overruntime_kind ∈ {server-url, process, remote};--integ/--disintegfast-path tolaunch-scaffolder provisionwhen on PATH -
✓
mintsubcommand — positional config,-o/--out,--stdout,--no-chmod -
✓
realignsubcommand — estate walk with--search-rootoverride,--dry-run,--check(CI),--keep-going, walk-error tolerant -
✓
provisionsubcommand —--integ/--disintegwith--all,--force,--no-confirm,--dry-run; bulk mode prompts before touching$HOME -
✓
configsubcommand —get/set/validate;setpreserves column alignment in the embedded metadata block -
✓ 17 unit tests passing across both crates
-
✓ 7 launchers fully managed: aerie, burble, game-server-admin, nqc, panll, project-wharf, stapeln
-
✓ 5 declared exceptions documented in
docs/launcher-exceptions-2026-04-10.md -
✓ Fixture-vs-live file-naming convention documented and enforced
-
✓ All 7 managed launchers regenerated with template delegation arms (2026-04-10)
Remaining work (ordered by current priority in STATE.a2ml):
-
❏ Golden-file regression tests pinning mint output for the 7 managed launchers
-
❏ macOS integration backend in
launch-scaffolder-common::integration -
❏ SPARK integration hook for
integrity.rsvia Zig FFI -
❏
standardsubcommand — still a scaffold stub -
❏
platformmodule — still a stub (runtime dispatch handled inside the generated script today) -
❏
integritymodule — still a stub (pending SHA-256 manifest generator) -
❏
exceptionsmodule — still a stub (per-app override merge) -
❏ Cross-platform CI (Linux/macOS/Windows matrix)
-
❏ Migration of the 5 declared exceptions once the template grows custom-mode hooks
# Standard cargo workflow
cargo build # debug build
cargo build --release # optimized, stripped, single-file binary
cargo test # run tests
cargo run -- --help # invoke the binary
# Or via justfile
just build
just test
just install # cargo install --path crates/launcherPMPL-1.0-or-later. See LICENSE.
All original hyperpolymath code is PMPL-1.0-or-later. Third-party
dependencies retain their original licenses; see cargo about output
(when added) for the full dependency license audit.
Jonathan D.A. Jewell (hyperpolymath)
j.d.a.jewell@open.ac.uk
Each consumer repo owns its own <app>.launcher.a2ml config at its
repository root. launch-scaffolder mint writes the generated
<app>-launcher.sh next to it. The pre-2026-04-10 pattern of pooling
launchers under /var/mnt/eclipse/repos/.desktop-tools/ is deprecated;
~/Desktop/Shortcuts/*.desktop files have been repointed at the new
per-repo paths.
Scaffolder-managed consumers (as of 2026-04-10):
-
stapeln — Visual Container Stack Designer (server-url, port 4010)
-
burble — WebRTC voice + control plane (server-url, port 4020)
-
aerie — network diagnostic suite (process)
-
game-server-admin — multi-game server orchestration (process)
-
nqc — NextGen Query Client, inside
nextgen-databases/(process) -
panll — panels framework (server-url, port 8000)
-
project-wharf — container/workload staging (process)
Declared exceptions (still hand-written; see
docs/launcher-exceptions-2026-04-10.md for reasoning and migration triggers):
-
hypatia,invariant-path,opsm,ambientops,idaptik
The bulk-realignment goal: once realign is implemented, one command will
re-mint every managed launcher in the estate against the current standard.