Skip to content

Command Line Tools

Installation

The wheels binary is distributed through four channels: Homebrew for macOS, .deb/.rpm packages for Linux, Scoop for Windows, and a direct JAR download for anyone who wants to wire it up by hand. Each channel installs the same two artifacts — the LuCLI launcher and the Wheels Module that ships the framework commands.

You’ll use this for:

  • First-time setup on a new machine.
  • Verifying that wheels --version runs before you start work.
  • Upgrading the CLI when a new LuCLI or Wheels Module release lands.
  • Troubleshooting PATH and JAVA_HOME issues on macOS Apple Silicon or a stripped-down Linux host.

The CLI runs on Java 21. On macOS the Homebrew formula installs openjdk@21 for you and sets JAVA_HOME inside the wheels wrapper. On Linux the .deb/.rpm package declares OpenJDK 21 as a runtime dependency, so apt/dnf pulls it in alongside wheels. The wrapper probes /usr/lib/jvm/ for a Java 21 install and exports JAVA_HOME itself. If you’re installing manually — or on Windows without Scoop — grab a JDK 21 build from Adoptium or Homebrew:

illustrative — system JDK setup
brew install openjdk@21
java -version

You should see openjdk version "21.0.x" in the output. On Apple Silicon macOS, Homebrew’s openjdk@21 isn’t symlinked into /usr/bin/java by default — the wheels formula handles this by exporting JAVA_HOME itself, but if you call java directly you’ll need to set it manually.

Tap the formula repository and install:

illustrative — Homebrew install
brew tap wheels-dev/wheels
brew install wheels

Homebrew 5.1+ asks you to trust third-party taps on first use — run brew trust wheels-dev/wheels once if prompted.

The formula (wheels-dev/wheels) pins a specific LuCLI release and a matching Wheels Module tarball. At install time, Homebrew downloads both and stages the module under $(brew --prefix)/opt/wheels/share/wheels/module/. It does not copy the module into your home directory yet — that happens on first run.

The first time you invoke wheels, the wrapper:

  1. Compares the staged module version against ~/.wheels/modules/wheels/.module-version.
  2. If the versions don’t match (or the destination doesn’t exist), it copies the module into ~/.wheels/modules/wheels/.
  3. Exports LUCLI_HOME=$HOME/.wheels so all runtime state — installed modules, running servers, resolved deps, stored secrets — lives under that directory and stays isolated from any standalone LuCLI install.
  4. Exports JAVA_HOME pointing at the Homebrew-installed openjdk@21.
  5. Execs the LuCLI launcher with your arguments.

On upgrades (brew upgrade wheels), the staged module version bumps; the next wheels invocation notices the mismatch and refreshes ~/.wheels/modules/wheels/ in place.

Wheels publishes signed native apt and yum repositories at https://apt.wheels.dev and https://yum.wheels.dev. The packages are built by nfpm on the release workflow, signed with the Wheels Distribution GPG key, and served from a Cloudflare R2 bucket per format. Add the source once; apt/dnf watches the repo and upgrades automatically.

Debian / Ubuntu — stable
curl -fsSL https://apt.wheels.dev/wheels.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/wheels.gpg
echo "deb [signed-by=/usr/share/keyrings/wheels.gpg] https://apt.wheels.dev stable main" \
| sudo tee /etc/apt/sources.list.d/wheels.list
sudo apt update && sudo apt install wheels
Fedora / RHEL — stable
sudo dnf config-manager --add-repo https://yum.wheels.dev/wheels.repo
sudo dnf install wheels

The package lays down /usr/bin/wheels (a shell wrapper), /opt/wheels/wheels (the LuCLI launcher, renamed at stage time so basename(argv[0]) is wheels and module dispatch routes correctly), /opt/wheels/module/ (the staged Wheels Module), /opt/wheels/.version and /opt/wheels/.channel (read by the wrapper for wheels --version), and /opt/wheels/sqlite-jdbc.jar (auto-staged into Lucee Express on first run). The wrapper mirrors the Homebrew flow: probes /usr/lib/jvm/ for OpenJDK 21, exports LUCLI_HOME=$HOME/.wheels, syncs the staged module into ~/.wheels/modules/wheels/ on first run or version mismatch, then execs LuCLI.

The bleeding-edge channel publishes a new package on every merge to develop, with a distinct package name (wheels-be) so it can coexist with stable on the same host:

Debian / Ubuntu — bleeding-edge
curl -fsSL https://apt.wheels.dev/wheels.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/wheels.gpg
echo "deb [signed-by=/usr/share/keyrings/wheels.gpg] https://apt.wheels.dev bleeding-edge main" \
| sudo tee /etc/apt/sources.list.d/wheels-be.list
sudo apt update && sudo apt install wheels-be
Fedora / RHEL — bleeding-edge
sudo dnf config-manager --add-repo https://yum.wheels.dev/wheels-be.repo
sudo dnf install wheels-be

Each .deb/.rpm carries its full SemVer version internally (e.g. 4.0.1~snapshot.1700 for snapshots), so dpkg --compare-versions and rpmvercmp correctly order pre-releases below the next GA. (GitHub Releases rewrites ~ to . in uploaded asset filenames, but the URLs you actually fetch from the apt/yum repos already use the canonical ~-form.)

Wheels ships on Windows through Scoop — a portable, sandboxed package manager that fits the project’s release cadence (hourly autoupdate for both stable and bleeding-edge channels). The legacy Chocolatey path is no longer supported; the old v1.x wheels package at community.chocolatey.org/packages/wheels is the CommandBox-based release and won’t drive a v4 tutorial.

Terminal window
# git is required by Scoop to clone any third-party bucket — Scoop's installer
# doesn't ship git. The main bucket (where git itself lives) is bundled with
# the Scoop installer, which is why this works from a totally fresh shell.
scoop install git
scoop bucket add wheels https://github.com/wheels-dev/scoop-wheels
# Pick a channel:
scoop install wheels # stable - tracks GA tags
scoop install wheels-be # bleeding-edge - tracks every develop merge
wheels --version

Both packages bundle OpenJDK 21 inline — no separate java bucket, no JAVA_HOME setup. Both expose the same wheels PATH shim, so Scoop refuses to install both at once. To switch channels:

Terminal window
scoop uninstall wheels && scoop install wheels-be
scoop uninstall wheels-be && scoop install wheels

See Release Channels for the full channel comparison.

The Scoop bucket lives at wheels-dev/scoop-wheels. New versions land in the bucket via autoupdate.yml, a self-hosted workflow that listens for a repository_dispatch event from this repo’s release.yml. End-to-end latency from upstream tag to user-installable manifest: ~5-7 min. A daily cron tick at 08:30 UTC catches any dispatch the workflow missed.

WinGet support (winget install Wheels.Wheels) is planned for post-v4.0.0 GA once a proper installer artifact is built. Until then, use Scoop.

If Homebrew and Scoop don’t fit your environment — a locked-down CI host, a Docker image, or a Windows box where Scoop isn’t an option — you can wire the pieces up directly. The install has two independent artifacts:

  1. LuCLI launcher. Download from github.com/cybersonic/LuCLI/releases. Pick the asset matching your OS — lucli-<version>-macos, lucli-<version>-linux, or lucli-<version>.bat on Windows. Rename it to wheels (or wheels.bat) so LuCLI’s binary-name detection activates the Wheels branding, and drop it somewhere on your PATH.

  2. Wheels Module. Download wheels-module-<version>.tar.gz (or .zip on Windows) from github.com/wheels-dev/wheels/releases. Extract it into ~/.wheels/modules/wheels/:

    illustrative — manual module extract
    mkdir -p ~/.wheels/modules/wheels
    tar -xzf wheels-module-*.tar.gz -C ~/.wheels/modules/wheels/
  3. Environment. Export JAVA_HOME to your JDK 21 install and LUCLI_HOME to $HOME/.wheels:

    illustrative — environment variables
    export JAVA_HOME=/path/to/jdk-21
    export LUCLI_HOME=$HOME/.wheels

    Persist these in your shell profile (~/.zshrc, ~/.bashrc, or $PROFILE on PowerShell).

Pick LuCLI and Wheels Module versions that are known to be paired — the Homebrew formula’s LUCLI_VERSION and MODULE_VERSION constants in Formula/wheels.rb are the canonical source for “what’s currently shipping together.”

If you already have standalone LuCLI installed and want to layer the Wheels module on top, LuCLI’s built-in installer can pull the same module zip the package managers ship — but only via --url=, not by bare name. The registry entry for wheels currently points at the wheels-cli-lucli mirror, which has been slow to sync from develop, so lucli modules install wheels without --url may land an older version. There is no wheels@be tag in the LuCLI registry; channel selection happens at the URL level.

stable — installs Wheels 4.0.0 GA
lucli modules install wheels --force \
--url=https://github.com/wheels-dev/wheels/releases/download/v4.0.0/wheels-module-4.0.0.zip
bleeding-edge — resolve the snapshot tag, then install
SNAP_TAG=$(curl -fsSL https://api.github.com/repos/wheels-dev/wheels-snapshots/releases \
| jq -r '.[0].tag_name | sub("^v"; "")')
if [ -z "$SNAP_TAG" ] || [ "$SNAP_TAG" = "null" ]; then
echo "Error: could not resolve snapshot tag. Check your network, GitHub API rate limit, or set SNAP_TAG manually." >&2
exit 1
fi
lucli modules install wheels --force \
--url="https://github.com/wheels-dev/wheels-snapshots/releases/download/v${SNAP_TAG}/wheels-module-${SNAP_TAG}.zip"

--force overwrites a previously installed version. The module lands in ~/.lucli/modules/wheels/ and is invoked as lucli wheels ….

After any of the four paths, confirm the binary resolves and reports a version:

Terminal window
wheels --version

You should see something like:

Wheels Version: 4.0.3

followed by the Wheels ASCII-art banner. The richer check is wheels version (no dashes), which reports the release channel and the JVM the wrapper picked up:

Wheels 4.0.3 (stable)
Java 21.0.11

If either line is missing or reports an unexpected value — say a Java major version other than 21 — jump to troubleshooting below.

Running wheels with no arguments is also a quick sanity check — it prints the same Wheels help banner as wheels --help. (wheels help falls through to LuCLI’s own generic help rather than the Wheels banner, so prefer wheels --help.) If you see a Component [modules.wheels.Module] has no function with name [main] error instead, you are running a build that predates this fix — upgrade to the latest 4.0.x release to restore the expected behavior.

Once the version check passes, wheels info inside a Wheels project gives you a deeper sanity check against a real app — datasource connection, environment, route count. See App Inspection for the full output and how to read it.

JAVA_HOME not set or points at the wrong JDK

Section titled “JAVA_HOME not set or points at the wrong JDK”

The Homebrew wrapper sets JAVA_HOME inside its own process, so a missing or wrong JAVA_HOME in your shell doesn’t break wheels. But if you’re calling java directly — or running a manual install — the CLI fails fast with a “Java 21 required” message when JAVA_HOME isn’t pointed at a JDK 21 install. On Apple Silicon macOS, Homebrew stages openjdk@21 at /opt/homebrew/Cellar/openjdk@21/<version>/libexec/openjdk.jdk/Contents/Home; export that path and re-run.

On macOS, Homebrew’s shim directory has to be on your PATH/opt/homebrew/bin on Apple Silicon, /usr/local/bin on Intel. Run brew --prefix to confirm, check your PATH, and add the shim directory if it’s missing. A fresh shell (exec $SHELL -l) often fixes this without any config change.

On Linux, the .deb/.rpm package installs /usr/bin/wheels, which should be on every user’s default PATH. If it isn’t, run dpkg -L wheels (Debian/Ubuntu) or rpm -ql wheels (Fedora/RHEL) to confirm the package landed and the binary is at the expected path. A reopened shell (exec $SHELL -l) clears any stale PATH cache from the previous session.

Conflict with a standalone LuCLI install sharing ~/.lucli/

Section titled “Conflict with a standalone LuCLI install sharing ~/.lucli/”

The wheels formula deliberately isolates runtime state under ~/.wheels/ (via LUCLI_HOME) so a standalone lucli install — which uses ~/.lucli/ — stays out of the way. If you previously had LuCLI installed directly and see odd module-resolution errors, check that the wrapper set LUCLI_HOME correctly (wheels system paths prints the resolved home directory and where it came from) and that ~/.wheels/modules/wheels/ contains a current Module.cfc and .module-version file.

Windows: there is no Resource provider available with the name [c]

Section titled “Windows: there is no Resource provider available with the name [c]”

On Windows, wheels new, wheels start, and most other subcommands crashed before any work could happen with:

lucee.runtime.exp.NativeException: there is no Resource provider available
with the name [c], available resource providers are [ftp, zip, tar, tgz,
http, https, ram, s3]

The cause was mixed-slash paths: java.io.File.getCanonicalPath() on Windows returns backslash form (C:\Users\tim\Projects), which — when concatenated with a forward-slash suffix — produced a string like C:\Users\tim\Projects/vendor/wheels. Lucee’s Resource API parsed c: as a URI scheme and bailed because no c provider is registered. This is fixed in the release that includes #2841. Update to the latest version and the error will not recur — scoop update wheels for Scoop installs, or re-fetch the latest Wheels Module (see Manual JAR install above) if you wired it up by hand. wheels --version was unaffected because LuCLI handles that flag before dispatching to the module.