Command Line

Ruzit ships as a Cargo workspace with two binaries: ruzit (the dev tool for scaffolding, testing, building, and packaging) and ruzitrun (the runtime / launcher template — runs games, serves as the EXE shipped games are cut from). Drop both on your PATH; the CLI calls the runtime as a library for test and copies the ruzitrun binary as a template for build.

ruzit init           [path]        scaffold a new project
ruzit initpackage    [path]        scaffold a Managed package folder
ruzit scaffold       [path]        regenerate .luaurc aliases from every manifest in the tree
ruzit test           [path]        run a Luau file
ruzit build          [path] [-o]   produce Generated/<exe> + Generated/Managed/*.managed
ruzit package        [folder] [-o] produce <id>.scripts.managed + <id>.assets.managed
ruzit fetch-deps     [path]        download steam_api into the given dir (default: cwd)
ruzit refresh-types  [path]        re-download types.d.luau into the given dir (default: cwd)
ruzit update         [path]        refresh ruzitrun from the configured release base (CLI itself is left alone)
                     --help        print this help

ruzitrun                              embedded launcher; runs the bundled .managed
                                      game when called with the appended trailer
ruzitrun --run <path>                 run a project from disk directly
ruzitrun --console                    attach/allocate a console for windowed launchers
The runtime is no longer self-dispatching. Earlier versions accepted ruzit init / Test / Build on the same binary. Those subcommands now live exclusively on the ruzit CLI. The ruzitrun binary handles only the running side: launcher mode (with appended trailer) or a direct --run <path> invocation.

ruzit init

ruzit init [path]

Creates a new project at path (current directory if omitted). Writes:

FileWhat's in it
build.tomlProject metadata and build settings.
Main.luauThe entry script the engine runs.
types.d.luauAPI type declarations for the Luau LSP. Always rewritten.
.vscode/settings.jsonWires luau-lsp up to types.d.luau, sets luau-lsp.require.mode to relativeToFile, and pins luau-lsp.platform.type to standard so the LSP doesn't try to apply Roblox conventions.
.luaurcStrict-mode flag plus an aliases map. Seeded with Game = "./" so require("@Game/scenes/level1") resolves from the project root regardless of which file is requiring it. Run ruzit scaffold to extend the aliases with one entry per package folder anywhere in the tree.
assets/Empty folder. Anything you drop in is auto-bundled.
Packages/Empty folder for third-party .managed drop-ins.
Re-running is safe. User files (build.toml, Main.luau) are skipped if they already exist. types.d.luau is always rewritten so your autocomplete tracks whichever Ruzit version you're on.

ruzit initpackage

ruzit initpackage [path]

Scaffolds a Managed package folder, the unit of distribution for DLC, mods, and shareable libraries. Writes:

Drop the folder next to your project's build.toml and ruzit build will pick it up as a DLC. See Packages & DLC for the full lifecycle.

ruzit scaffold

ruzit scaffold [path]

Regenerates .luaurc aliases by walking the entire project tree. Any folder containing both a ManagedInfo.toml and its declared entry file is treated as a package: the manifest's ID becomes an alias pointing at the folder. require("@PkgId") then resolves to the entry file inside via Luau's directory init lookup, and submodules are reachable as require("@PkgId/sub/module"). The entry file's existence is still checked — packages whose declared Entry is missing are skipped.

The walk is recursive but skips .git, Generated/, target/, node_modules/, and any dotfile directory. When a folder with a manifest is found the walk stops descending into it (packages don't nest aliases inside themselves).

Re-run any time you add, remove, or rename a package. The file is overwritten in full, so any aliases you added by hand get blown away — keep custom aliases in a separate file or commit them back into .luaurc after each scaffold.

The Game = "./" alias is always re-emitted. Example:

-- Packages/cool-math/ManagedInfo.toml has ID = "CoolMath"
-- Packages/cool-math/init.luau exists
-- libs/utils/ManagedInfo.toml has ID = "Utils" (a hand-placed package)

$ ruzit scaffold
[Ruzit] scaffold -> /work/MyGame (scanning recursively)
  alias  CoolMath -> ./Packages/cool-math
  alias  Utils    -> ./libs/utils
[Ruzit] scaffold done: wrote .luaurc (3 aliases)

-- in your code:
local math   = require("@CoolMath")            -- -> Packages/cool-math/init.luau
local vec    = require("@CoolMath/vector")     -- submodule inside the package
local menu   = require("@Game/scenes/menu")
Scaffold is purely tooling. The aliases it writes only affect luau-lsp autocomplete and Luau's own require resolution — they don't change what the runtime loads. At runtime, require("@PkgId") still goes through the Managed package loader.

ruzit test

ruzit test [path]

Runs a project from source. No packaging, no encryption, the engine reads your .luau files directly off disk and the next edit hits the next run.

With no path, the engine searches for Main.luau next to the Ruzit executable, then in the working directory, then walks parent folders. Pass an explicit path to override:

ruzit test                       # auto-find Main.luau
ruzit test ./MyGame              # project dir -> MyGame/Main.luau
ruzit test ./MyGame/scenes/level1.luau

Any DLC folders adjacent to the project (folders containing ManagedInfo.toml) are loaded too, same as in a packaged build.

ruzit build

ruzit build [path] [-o output] [--console]

Packages a project for shipping. Output layout:

Generated/
    MyGame.exe                          # launcher (ruzitrun binary + JSON trailer)
    steam_api64.dll                     # Steam redistributable (Windows)
    Managed/
        MyGame.scripts.managed          # encrypted .luau bundle
        MyGame.assets.managed           # encrypted asset bundle
        MyGame.assets.shard0001.managed # sharded asset bundles (if enabled)
        MyDlc.scripts.managed           # discovered DLC, packaged in
        MyDlc.assets.managed

Ship the entire Generated/ folder. The launcher reads its own JSON trailer at startup, finds Managed/ next to itself, and boots the bundled scripts.

Flags

FlagEffect
-o <dir> Write output to <dir> instead of ./Generated.
--console Attach (or allocate) a console for print() output. Useful when build.toml sets exe-windowed = true and you still need stdout for debugging.
Per-platform launchers. ruzit build always produces a launcher for the OS Ruzit is running on, it's a copy of the running binary plus a JSON trailer. To ship Windows + Linux + macOS, build Ruzit on each OS (or use the GitHub Actions matrix), then run ruzit build on each.

ruzit package

ruzit package [folder] [-o output]

Packages a single Managed folder into .scripts.managed + .assets.managed files, the format used by DLC and Workshop drops. The folder must contain a ManagedInfo.toml.

cd MyMod
ruzit package                  # -> MyMod/Generated/Managed/<id>.scripts.managed
ruzit package . -o ../release  # custom output dir

Players install your package by copying the .managed files into a host game's Packages/ folder. The host game's runtime mounts them as require("@your-id/...").

ruzit fetch-deps

ruzit fetch-deps [path]

Download the Steam SDK redistributable (steam_api64.dll on Windows, libsteam_api.so on Linux, libsteam_api.dylib on macOS) into the given directory (current directory if omitted). The Steamworks SDK is not bundled with Ruzit, you obtain it from partner.steamgames.com, host the redist binary somewhere reachable, and point the CLI at it via:

RUZIT_STEAMSDK_URL=https://your.host/path/steam_api64.dll
ruzit fetch-deps .

Without the env var set, the command exits with an explanation rather than guessing a URL. Drop the resulting file alongside your ruzit binary (or your shipped game's launcher EXE), Ruzit only loads Steam when it sees the redist file next to the executable AND either runs through Steam or runs in test mode. See Steam for the runtime gating logic.

ruzit refresh-types

ruzit refresh-types [path]

Re-download types.d.luau into the given directory (current directory if omitted). The CLI no longer ships a bundled copy of the type declarations, every fresh ruzit init fetches them from https://raw.githubusercontent.com/thekingofspace/Ruzit/master/types.d.luau so a newly-cloned project always picks up the latest API. This command is the on-demand version, useful when you've updated the runtime and want hover-docs / autocomplete to reflect the new APIs without re-running init.

# default: pull latest from master
ruzit refresh-types

# pin to a tagged version of the types
RUZIT_TYPES_URL=https://raw.githubusercontent.com/thekingofspace/Ruzit/v4.0.0/types.d.luau \
ruzit refresh-types

# or point at your own fork / mirror
RUZIT_TYPES_URL=https://example.com/types/v4.0.0.luau \
ruzit refresh-types
Init is offline-tolerant. If the URL is unreachable when you run ruzit init, the CLI writes a small stub explaining how to retry instead of failing the whole init. Run ruzit refresh-types once you're online to drop in the real declarations.

ruzit update

ruzit update [path]

Refresh the local ruzitrun runtime binary from the configured release host. The ruzit CLI itself is deliberately not touched, so the running CLI doesn't have to swap itself out and there's no ruzit.update.exe staging dance on Windows. To upgrade the CLI, reinstall it manually (download a new zip and replace ruzit.exe) or run a fresh install once.

# Default: pull the latest stable runtime straight from GitHub.
# No env var needed, the CLI ships pointing at the official repo.
ruzit update

# Or pin to a specific tagged version:
RUZIT_RELEASE_BASE=https://github.com/thekingofspace/Ruzit/releases/download/v4.0.0
ruzit update

# Or point at your own fork / mirror:
RUZIT_RELEASE_BASE=https://example.com/ruzit/v4.0.0
ruzit update

The CLI fetches a single platform-specific zip and extracts only the runtime binary inside it. The zip's contents are deliberately plain-named so a manual download -> extract leaves you with files you can run as ruzit init / ruzitrun --run … straight away:

{base}/ruzit-windows.zip   ->  ruzit.exe + ruzitrun.exe   (update extracts ruzitrun.exe)
{base}/ruzit-linux.zip     ->  ruzit + ruzitrun           (update extracts ruzitrun)
{base}/ruzit-macos.zip     ->  ruzit + ruzitrun           (when built)

The default base URL is https://github.com/thekingofspace/Ruzit/releases/latest/download — GitHub's own redirect that always points at the most recent stable release. So a fresh install can run ruzit update with no setup. The bundled GitHub Actions workflow auto-publishes versioned releases on every push, see Release flow below.

Don't run ruzit update while a game is open. On Windows the runtime exe is locked while it's running, so close any Ruzit Test session or shipped game built on the same ruzitrun.exe before updating.

self-update is kept as an alias of update for back-compat.

Release flow

The repo ships .github/workflows/release.yml which builds Windows + Linux binaries, combines them into a single artifact, and creates a GitHub Release with the four binaries attached. Two trigger modes:

TriggerWhat happens
Push to main / master Reads the version from runtime/Cargo.toml (e.g. 4.0.0), builds the binaries, force-tags the current commit as v4.0.0, and publishes a normal release marked --latest on GitHub. Both ruzit-windows.zip and ruzit-linux.zip are attached.
Push an explicit v* tag Builds at that exact tag and publishes the same way. Useful for backfill releases or when you've already bumped Cargo.toml and want to release from a non-tip commit.

Releases are never marked as prereleases. The workflow uses gh release create --latest, so GitHub's /releases/latest/ redirect resolves automatically to whatever the most recent push produced — which is what the CLI's default RUZIT_RELEASE_BASE targets.

To cut a new version: bump version = "4.0.0" -> "1.1.0" in Ruzit/runtime/Cargo.toml (and the matching core + cli Cargo.tomls), commit, push. The workflow tags + releases v1.1.0 automatically.

No secrets to configure, the workflow uses the built-in GITHUB_TOKEN. Permissions are scoped via permissions: contents: write at the workflow level, so it can create / delete releases on its own repository.

Global flags

FlagEffect
--console Attach the parent console (or allocate a new one). Works on packaged launchers too, useful when a windowed game needs an emergency stdout.
--help / -h Print the usage banner.