Skip to content

Add quadrantmc, a CLI for Quadrant, and ship it with the desktop packages - #68

Merged
mrquantumoff merged 32 commits into
QuadrantMC:nextfrom
mrquantumoff:feat/cli
Sep 29, 2026
Merged

mrquantumoff merged 32 commits into
QuadrantMC:nextfrom
mrquantumoff:feat/cli

Conversation

@mrquantumoff

Copy link
Copy Markdown
Collaborator

Adds quadrantmc, a command-line client that does everything the desktop frontend does, and ships it with the Linux packages, the Windows installer and the Microsoft Store package.

The CLI

src-tauri/crates/quadrant-cli is a thin layer over QuadrantHost, like quadrant-napi. It shares the desktop app's data folder and keyring, so settings, modpacks and the Quadrant ID login are the same in both. quadrant-core and quadrant-host are unchanged.

Commands cover modpacks (create, edit, apply, clear, export, import a share code, updates, identify, register, share), mods and packs (search across both providers, info, deps, owners, install, remove, update), installed resource and shader packs, Prism instances, Quadrant ID sign-in through the same loopback flow as the Account page, notifications, sync, settings, news, telemetry, deep links (quadrantmc open <link>) and a raw invoke passthrough. Every command takes --json. The updater and window-only settings are out of scope. Usage is in docs/cli.md.

Frontend-only logic is ported as tested pure functions: modpack ordering, search provider choice and result merging, deep-link and share-code parsing, notification filtering, and the lastSettingsUpdated bump that settings sync depends on.

Where a terminal can't show what the app shows, the CLI behaves more conservatively:

  • mod install defaults to the target modpack's version and loader instead of the last-used ones.
  • Replacing a local modpack on import or pull, overwriting an export and deleting all ask first, or need --yes.
  • modpack updates fails when some mods couldn't be checked.

Packaging

src-tauri/tauri.cli.conf.json builds the CLI (scripts/build-cli.ts) and bundles it as a sidecar. bun run build:tauri, release.yml, msstore.yml and the Linux shell check in validate-desktop.yml pass it with --config. It is not in tauri.conf.json because tauri-build fails when a sidecar file is missing, which would break cargo test, rust-analyzer and tauri dev.

  • deb and rpm install /usr/bin/quadrantmc. The AUR template now installs it too. Flatpak picks it up from the deb (flatpak run --command=quadrantmc dev.mrquantumoff.mcmodpackmanager).
  • The NSIS installer puts quadrantmc.exe in the install folder, and hooks in src-tauri/windows/ add that folder to the user PATH and remove it on uninstall. The PATH change is idempotent, because the updater re-runs the installer, and it keeps the REG_EXPAND_SZ entries unexpanded.
  • The MSIX gets a hidden second application with a console execution alias, quadrantmc. msixbuild.ps1 and msstore.yml stage the per-arch exe.
  • AppImage and macOS bundle it without putting it on PATH.

Verification

  • cargo fmt --check, clippy (no new warnings), and cargo test --workspace (55 new CLI tests) pass. The workspace tests also pass without src-tauri/binaries/ present.
  • The binary was run against a scratch data folder and a separate keyring service. That covered Modrinth and CurseForge installs, updates, export, identify, search, deep links, content and settings.
  • A local NSIS build contains quadrantmc.exe and the PATH script. The PATH script was tested against a scratch registry value.
  • makeappx pack accepts the staged MSIX.

Not verified locally:

  • deb and rpm builds, which rely on this PR's CI.
  • The Windows installer and the MSIX were never actually installed.
  • The ARM64 and macOS builds.

Builds a host on the desktop app's data folder (or --data-dir), prints
results as text or --json through one Report/Output path, renders host
progress events on stderr and translates i18n error keys. First
commands: versions, news, telemetry, invoke.
settings list/get/set/unset/mc-folder/push/pull. Every config write bumps
lastSettingsUpdated like the desktop frontend's configChanged handler, so
settings sync still sees CLI edits as newer. Text settings stay text even
when the value looks like JSON, and toggling collectUserData sends or
withdraws telemetry as Settings.tsx does.
modpack list/show/create/edit/delete/apply/clear/export/updates/identify/
register/share/import/folder. Ports the frontend's modpack ordering,
name sanitizing and share-code parsing as tested functions, parses
--loader and --source into core types at the clap boundary, and looks
mods up per provider through one dispatch module.
The manifest's version label is a date on CurseForge and an opaque id on
Modrinth; the decoded file name from the download URL tells the user
which build is installed.
mod search/info/deps/owners/install/remove/update/categories. Search
ports the search page's provider selection (settings, category locks,
open-source, loader support), category merging and result ordering;
install ports the install page's modpack/version/loader defaults and
remembers explicit choices in lastUsed* like its pickers.
Resource and shader packs don't depend on a mod loader.
content list/copy/delete/folder over the host's content locations, with
copy skipping packs the destination already has as the installed-content
page does. prism list/plan/apply/detach; while experimentalFeatures is
off they say so instead of surfacing the host's refused-request error.
Boolean settings are now read through one config helper.
Chooses the OS keyring service the Quadrant ID login is read from,
matching the N-API addon's keyring_service_name, so a sandboxed run can
be kept away from the desktop app's login.
account login/logout/info/open/register. login ports the account page's
OAuth flow: a random state kept in oauthState, a one-shot listener on
the first free of 127.0.0.1:4000-4005 that ignores stray requests like
the favicon fetch, and a five-minute timeout. The link is always
printed, for when no browser opens on its own.
Ports resolveDeepLink with its test cases: curseforge://install,
modrinth:// project links (including wrapped website links),
quadrantnext:// mod, modpack and login links, and usequadrant.dev share
links. Mod links run the same install as mod install, modpack links the
same import as modpack import, and login links finish a sign-in only
when their state matches the stored oauthState.
notifications list/read/accept/decline/watch. Ports the notification
popover's filtering (modpack update notices hidden when turned off or
made by the user) and invite parsing (invite id from the message or the
resource id, inviter from the message text). watch runs the app's
background workers and prints new notifications until Ctrl+C.
sync list/push/pull/members/invite/kick/delete/share. push reports a
newer cloud copy with the way out (--force or sync pull) instead of the
bare conflict; pull installs the cloud copy under the linked local
modpack's name, paired as the apply page pairs them, and records the
sync date.
A CLI output path can name a folder that doesn't exist yet, unlike the
desktop save dialog.
Subcommand help listed the global flags among its own options.
docs/cli.md covers building, the global options, every command group,
deep links, and how to sandbox a run away from the desktop app's data.
The README's feature list points at it.
The crate stays quadrant-cli; the binary, its help and the commands its
hints and docs print now use quadrantmc.
The post-install hook adds the install folder to HKCU\Environment Path
and the post-uninstall hook removes it, so a sidecar installed there is
callable from new terminals. user-path.ps1 holds the logic: it reads Path
unexpanded, writes it back as REG_EXPAND_SZ, matches entries
case-insensitively and ignoring a trailing backslash, and leaves Path
alone when it is already in the wanted state, since the updater re-runs
the installer.
src-tauri/tauri.cli.conf.json runs scripts/build-cli.ts after the
renderer build, which builds quadrantmc for TAURI_ENV_TARGET_TRIPLE and
copies it to src-tauri/binaries/ for bundle.externalBin. It also turns on
the NSIS PATH hooks. It stays out of tauri.conf.json because tauri-build
fails when an externalBin file is missing, which would break cargo test,
rust-analyzer and tauri dev.

bun run build:tauri, the macOS ad-hoc build, the release workflow and the
Validate Desktop shell build merge it in, so deb, rpm, AppImage, NSIS and
the macOS app all ship the CLI.
package() copies binaries one by one, so the sidecar the rpm now ships
at /usr/bin/quadrantmc needs its own install line.
AppxManifest.xml gains a second, hidden application for quadrantmc.exe
with a console app execution alias, so Store installs can run quadrantmc
from a terminal. makeappx requires SupportsMultipleInstances for a
console alias.

msixbuild.ps1 stages quadrantmc.exe next to quadrant_next.exe for each
architecture, taking it from src-tauri/binaries first, and fails when it
is missing. The Store workflow builds with tauri.cli.conf.json, uploads
each architecture's CLI and stages it for the bundle job.
docs/cli.md says where each desktop package puts the CLI and how to call
it, and how packaging builds bundle it. AGENTS.md notes that
bun run build:tauri includes the CLI and plain tauri build does not.
@mrquantumoff
mrquantumoff merged commit 6d52f7a into QuadrantMC:next Sep 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant