The shared foundation of the *Utility line — Windows terminal tools with a common look and the same plumbing underneath:
| Product | Status |
|---|---|
| DiskUtility | shipping (the crate was extracted from it) |
| AccountUtility | next |
| MouseUtility | planned |
| KeyboardUtility | later |
| Module | Purpose |
|---|---|
AppInfo / init |
Product identity (name, display name, GitHub repo, tagline, version, build time), registered once at startup |
ui |
Theme (one accent per product over shared neutrals), header with update/elevation badges, footer key hints, centred modal frame, spinner |
shell |
The app loop: Shell state + Product trait; owns the Shift+U update dialog, ? help, c copy-log, quit/restart, status expiry; launch, snapshot/render for --snapshot and layout tests; shared confirm/input/busy drawers |
cli |
--version, --update, --no-update-check / <NAME>_NO_UPDATE_CHECK, auto-update on launch |
update |
Self-update from GitHub releases: repo URL allowlist, SHA-256 against checksums.txt, PE header + --version probe, rename-swap, relaunch |
config |
%APPDATA%\<name>\config.json load/save for any serde struct, plus CoreSettings (auto_update, notify) |
logger |
<name>.log next to the exe, 5 MiB rotation, copy-to-clipboard |
ps |
Hidden-window Windows PowerShell with a hardened prelude, CLIXML error decoding, elevation check |
notify |
Windows toast notifications without extra dependencies |
text |
human_bytes, fit, fmt_elapsed |
The Windows-only modules (ps, update, notify) are gated on cfg(windows);
the rest builds anywhere.
./scaffold.ps1 -Name accountutility -DisplayName 'Account Utility' `
-Tagline 'switch Steam · Epic · Discord accounts' `
-Description 'A terminal account switcher for Windows.' `
-Theme ACCOUNT -Dest G:\AccountUtilityThis copies templates/ with the placeholders filled in, runs git init,
builds it and prints accountutility v0.1.0 (built …). The result already has:
- a working TUI skeleton on
utility_core::shell(src/main.rs,src/app.rs,src/ui.rs): list panel, a product modal, background scan,--snapshot, and two tests — header, footer, help,Shift+Uupdate dialog andccopy log come from the shell; build.rsbaking the compile time in;- CI (clippy
-D warnings, tests,--versionsmoke test); - a CHANGELOG-driven release workflow (tag
vX.Y.Z→ build → GitHub release with<name>.exe+checksums.txt→ winget submission whenWINGET_TOKENis set); install.ps1one-liner with checksum verification;- winget manifest generation and submission scripts driven by
packaging/product.ps1; packaging/release.ps1that checks CHANGELOG/Cargo.toml/lockfile/tree, then tags and pushes.
Pass -LocalCore to depend on this checkout by path while utility-core itself
is changing; otherwise the product pins the git tag in its Cargo.toml.
[dependencies]
utility-core = { git = "https://github.com/viorizz/utility-core", tag = "v0.2.1" }static APP: utility_core::AppInfo = utility_core::AppInfo {
name: "accountutility",
display_name: "Account Utility",
repo: "viorizz/accountutility",
tagline: "switch Steam · Epic · Discord accounts",
version: env!("CARGO_PKG_VERSION"),
build_epoch: env!("BUILD_EPOCH"),
};
fn main() {
if let Some(code) = utility_core::cli::handle_common_args(&APP) {
std::process::exit(code);
}
utility_core::init(&APP);
let cfg: Config = utility_core::config::load();
let mut app = App::new(cfg); // App embeds a `shell::Shell` and implements `shell::Product`
std::process::exit(utility_core::shell::launch(&mut app).unwrap());
}The Product impl is the whole surface a product writes: draw (header/panels/
footer/own modals), on_key (return true when consumed — always while one of
your modals is open), on_tick (drain your channel), hints, and config access
for the update dialog's auto-update toggle. See templates/src/app.rs for a
complete example, including the --snapshot flag and a frame test via
shell::snapshot.
Release contract for the self-updater: every GitHub release ships
<name>.exe and a checksums.txt line "<sha256> <name>.exe", and the
binary answers --version with <name> v<version> …. The templates do all
of this.
Add a ## vX.Y.Z section to CHANGELOG.md, bump Cargo.toml, commit, then
git tag vX.Y.Z && git push origin vX.Y.Z. Products update the tag in
their Cargo.toml.
MIT