xx
.md

Keep your build. Make its environment predictable.

xx reduces "works on my machine" problems without requiring a different way to build your project. Declare the tools your build needs, then run your existing commands.

xx downloads supported tool versions and starts each task with a controlled environment instead of inheriting your shell's tools and settings. Your build still runs on your machine, against your normal checkout. Existing tools still discover files, manage their dependencies, and compile the project as usual.

xx does not require a container, a virtual machine, or a declaration for every file that a command reads. It controls the task's starting environment to prevent accidental dependencies on local setup while keeping ordinary development workflows usable.

What xx controls

Managed CMake, Go, Ninja, Node.js, Protobuf, and Zig packages are supported. More language and tool packages can be added without changing the core model.

Install

Using Go

go install withxx.dev/xx@latest

Install Scripts

Unix

curl --proto '=https' --tlsv1.3 -sSf https://withxx.dev/scripts/install_xx.sh | sh

The installer upgrades an existing xx found on PATH. New installations use $HOME/.local/bin.

Set XX_INSTALL_DIR to override both locations:

export XX_INSTALL_DIR="$HOME/bin"
curl --proto '=https' --tlsv1.3 -sSf https://withxx.dev/scripts/install_xx.sh | sh

If the selected directory is not on PATH, the installer prints a warning but does not modify shell configuration.

Windows

Invoke-WebRequest -UseBasicParsing https://withxx.dev/scripts/install_xx.ps1 | Select-Object -ExpandProperty Content | Invoke-Expression

The installer upgrades an existing xx.exe found on PATH. New installations use %LOCALAPPDATA%\Programs\xx\bin.

Set XX_INSTALL_DIR to override both locations:

$env:XX_INSTALL_DIR = "$HOME\bin"
Invoke-WebRequest -UseBasicParsing https://withxx.dev/scripts/install_xx.ps1 | Select-Object -ExpandProperty Content | Invoke-Expression

If necessary, the installer adds the selected directory to the user PATH. Machine-wide PATH is not modified.

If no platform user directory can be determined, installation fails. Set XX_INSTALL_DIR to provide the destination explicitly.

First Project

From a project directory, initialize xx:

xx init

This creates .xx/main.star. Entrypoints live in .xx/ and can use .star, .build, .starlark, .bzl, or .bazel as their extension.

For a Go project that already uses go test ./..., use:

load("@go@1.26.5", "go")
load("@os@1", "os_run")

os_run(("go", "test", "./..."), deps=[go])

Run the entrypoint by its base name:

xx run main

On first use, xx downloads the requested official Go SDK. The command runs with that SDK rather than a Go installation from your shell. See Go examples for go_binary, go_test, and other managed Go commands.

xx run exits 0 on success and 1 on failure. Pressing Ctrl-C cancels and drains running tasks, prints no interrupt diagnostic, and exits 130 unless another error is retained.

Install Cobra's generated shell completion with xx completion bash, xx completion zsh, xx completion fish, or xx completion powershell. Completion for xx run suggests unambiguous entrypoint names from .xx/; for example, xx run b completes to xx run build when build is the only matching entrypoint.

Project Secrets

See the builtin secret manager for its project scope, storage model, and task workflow.

Store a secret for the current project and retrieve it later:

xx secrets set TOKEN "secret value"
xx secrets get TOKEN

Omit the value to read it exactly from standard input:

pbpaste | xx secrets set STRIPE_WEBHOOK_SECRET

Piped or redirected standard input is stored unchanged, including any trailing newline. xx secrets get writes the stored value unchanged. When standard input is a terminal, provide the value argument instead.

List the keys stored for the current project without exposing their values:

xx secrets list

Each key is printed on its own line. Keys containing control characters, quotes, or backslashes are quoted and escaped.

Print the SQLite database location with xx secrets db.

Remove a secret from the current project with xx secrets rm TOKEN. The command fails if the key is not set.

Build definitions can load stored secrets into dependent task environments with xx_secrets.

Secrets are scoped to the canonical path of the current project. Values are encrypted in xx's operating-system application-data database, and the encryption key is kept in the operating-system keyring. Setting an existing key replaces its value.

The database is stored at $XDG_DATA_HOME/xx/secrets.db on Linux (or $HOME/.local/share/xx/secrets.db when XDG_DATA_HOME is unset), ~/Library/Application Support/xx/secrets.db on macOS, and %AppData%\xx\secrets.db on Windows.

The value argument passed to xx secrets set may remain visible in shell history or process inspection provided by the operating system. Use standard input to avoid exposing the value as a command-line argument. Quote argument values containing spaces or shell metacharacters.

Interactive Runs

xx run opens an interactive terminal UI when CI is empty or unset and both input and output are connected to a terminal. Setting CI to a non-empty value or redirecting input or output uses plain mode instead. In plain mode, command standard output and standard error are merged and streamed to standard output; commands do not receive input.

Interactive mode groups tasks under each requested entrypoint, preserving argument order and duplicates. Every built-in function invocation appears as a command or progress row, and tool setup may add another progress row. Commands are selectable; progress rows are not. Starlark print output gets its own row. Rows show compact labels such as go@1.26.5 > go_binary: .xx/out/app and one of these states:

The first running command is selected automatically. Click any command row, including a completed command, to view its retained output. Progress rows are not selectable. The mouse wheel scrolls the task list or selected task output, depending on pointer position. Commands can use terminal colors and prompts.

Keyboard input and paste go only to the selected running command. Mouse input is not forwarded to commands, and task selection requires the mouse. Ctrl-C cancels the whole run rather than the selected command. xx restores the terminal and exits 130 unless another error occurred; errors exit 1 with a normal diagnostic.

Drag across task output to select and copy text. The copied text excludes the task list and pane borders. Output stays still while selected, but tasks keep running. Press Escape to clear the selection and resume live output. Ctrl-C copies selected text instead of canceling the run. Clipboard copying requires terminal support for OSC 52.

Right-click an HTTP or HTTPS link to copy its complete URL, even when the link wraps across lines. Links are also clickable through your terminal's link-opening gesture, such as Command-click on macOS. Clickable links require terminal support for OSC 8.

After success, xx restores the terminal and prints every command and progress row in a compact summary grouped by entrypoint. After failure, it prints output from the selected failed task, or the first failed task with output, followed by the diagnostic. Pass -v or --verbose to print every task heading and its retained output instead of only one failed command after the UI closes. Verbose mode does not change plain-mode output. Task output is available only for the current run and retains up to 10,000 lines per task.

Where Next?

Project Status

xx is early software. Managed CMake, Go, Ninja, Node.js, Protobuf, and Zig packages support Windows x86-64, Windows ARM64, Linux x86-64, Linux ARM64, and macOS ARM64. Tool release availability can impose additional version-specific limits.

Source and issue tracking are on Codeberg. xx is available under the Zlib License.