CLI Commands

This page documents the six {baler} subcommands: what each one does, when you’d reach for it, and what its flags actually change. It’s written for people using {baler} to manage {box} modules, not for people working on {baler}’s own source.

Every subcommand exits non-zero and prints error: ... to stderr on failure, so you can rely on that in scripts or CI without parsing output.

Initialization and Scaffolding

Through baler init command, this will set up a module project. It works in two ways, depending on whether you give it a name:

  • With a <name>, it scaffolds a new module project from scratch, in a new directory.
  • Without one, it initializes the directory you are already in, like uv init or cargo init.

Either way, baler.toml is the most required file to interact with {baler}. Here’s the anatomy of baler init:

# A new project, in a new directory
baler init <name> [--dir-name <dir>] [--native <lang>] [--backend <backend>]

# The current directory, in place
baler init [--name <name>] [--src <dir>] [--native <lang>] [--backend <backend>]

And then this following table explains how baler init behaves:

Argument Description
<name> The module’s name. Used as the default project directory name (<name>-proj), the default source directory name, and the name field written into baler.toml. Leave it out to initialize the current directory instead.
--dir-name <dir> Only with <name>. Use a different project directory name instead of <name>-proj.
--name <name> Only without <name>. The module’s name, instead of the one worked out from the current directory (see The current directory). Anything goes; it isn’t checked.
--src <dir> Only without <name>. The directory holding the module’s source, relative to the current directory. Written as src in baler.toml, unless it is the module’s own name, which is the default. It must be an existing directory inside the project.
--native <lang> Scaffold a module with compiled code instead of pure R: c for C, cpp for C++. Sets up a starter source file, build configuration, and the loader code that runs dyn.load() automatically. Leave this out for a plain R module.
--backend <backend> Only relevant with --native cpp. Which C++ binding library to scaffold: rcpp (the default) or cpp11.

When --native is not passed, this will ignore --backend parameter, and won’t generate the files under the chosen language, just their pure R versions.

A new project

Once executed with a <name>, the following files:

  1. baler.toml
  2. README.md
  3. And R source files under a folder containing the source code of the module

are generated. Fails without changing anything if the target directory already exists, so it’s safe to run and see the error rather than risk overwriting something.

Here’s an example:

baler init stats_native

The current directory

Without a <name>, baler init sets up the directory you are in. It always writes baler.toml there. If baler.toml already exists, it stops with an error and writes nothing.

There are two cases of what else it does depends on the directory:

  • When the directory is empty (not counting the hidden entries such as .git), it also adds README.md and an example module, in a folder named after the module. This is the same layout as a new project, only in place. --native and --backend apply here.

  • Otherwise, when it is not empty, i.e. you already have the source code files and/or anything, it writes baler.toml and nothing else. Your files are left as they are, and no example module or README is added. --native is rejected here, because scaffolding native code into a project that already has files could overwrite them. Add the native code yourself instead; it is detected automatically.

The module’s name

The module’s name is the directory’s name, as is. For example, when the current directory you initialized is named weather/ will give weather, and tidyverse-module/ gives tidyverse-module. baler init do not strip or check anything. That’s because a name that isn’t syntactic in R, such as my-module, still works, since box::use() accepts it in backticks. To use a different name, pass --name or edit name in baler.toml.

The source directory

src in baler.toml says where the module’s source lives. By default it is a folder named after the module. If your module is in a different folder, pass --src <dir> or edit src in baler.toml.

If the source directory doesn’t exist, baler init prints a warning and still writes baler.toml. Keep in mind that baler bundle needs an __init__.r in that directory, which baler init doesn’t create for code that is already there.

Examples

Here are three common situations.

  1. You are in an empty directory called weather/. Running baler init gives you a module named weather, plus README.md and an example module in weather/weather/. The same goes for a directory called tidyverse-module/, where the module is named tidyverse-module.

  2. You are in an existing weather/ project whose source code is in weather-module/:

    └── weather/
        ├── LICENSE.md
        └── weather-module/
            ├── __init__.r
            └── submodule/
                ├── __init__.r
                └── source.r

    The module is named weather, but the source isn’t in a folder with that name, so point baler init at it:

    baler init --src weather-module

    baler.toml is the only new file, and it records the source directory as src = "weather-module".

  3. You are in tidyverse-module/, but you want a different module name. Pass --name:

    baler init --name tidyverse

Native compiled code compilation

As what the official documentation of {box} R package said, one module can handle native compiled codes. {baler} gives you an alternative approach to manage module’s native code (C, C++, …) right where the source lives, for quick local iteration while developing. It never deletes the source afterward, which is what sets it apart from the compile step that happens automatically during install. For this one, use baler compile and here’s the following anatomy:

baler compile [<path>] [--clean] [--rebuild]

The following table summarizes the following bracketed “parameters” above:

Argument Description
<path> The project’s root directory. If you leave this out, it defaults to the current directory, so plain baler compile works from inside a project.
--clean Deletes this module’s compiled output and anything cached from previous builds, then stops without compiling anything. Use this when you just want a clean slate, not a new build.
--rebuild Throws away anything cached from a previous build first, then compiles again for real, even if nothing in the source actually changed. Useful when you’ve upgraded your compiler or toolchain and want to confirm the module still builds correctly, since an unchanged build would otherwise just reuse the old result.

A plain baler compile, with neither flag, only recompiles when the source has actually changed since the last build; otherwise it reuses the previous result to save time. --clean and --rebuild do opposite things and can’t be used together, passing both is an error.

Bundling and Packaging

Similar to pkgbuild::build() (or devtools::build()) and an analogue for R CMD build, baler bundle now bundles a module’s source, creating into a single .tar.gz archive that can be installed elsewhere, shared with someone else, or attached to a GitHub release. Here’s the anatomy of the baler bundle command:

# If `<path>` isn't supplied
# Then `.` argument is passed by default
baler bundle [<path>]

The only argument for this command is the <path>, which refers to the project’s root directory, wherever baler.toml lives.

It is decided that the --binary or --keep-source are now dropped. Now, what the archive contains is decided by [compiled-code] in baler.toml through the following:

  • When binary = true is declared under the [compiled-code] from baler.toml, {baler} will compile any native code in the module first, and include the compiled artifact in the archive. Without it, only source is bundled, and compiled code has to be built again on install.

  • When keep_source = true is declared under the [compiled-code] from baler.toml, the native source code will be kept on the archives. This is only valid together with binary = true.

The resulting archive is named <name>_<version>.tar.gz (<name> is what you set as name under [project] in baler.toml), and written to whatever directory you ran the command from. The execution fails if the source directory is empty, or if baler.toml is missing or can’t be parsed. A bundle built with binary = true only works reliably on the exact platform and R version it was built for. There’s currently no automatic check that warns you, or falls back to compiling, when installing one on a mismatched machine, even if keep_source = true was set at bundle time.

Module installation

As long as the {box} module is configured by {baler}, you can install that {box} module so that other machines and/or projects can load it with box::use(). This is {baler}’s equivalent of install.packages().

baler install <name> --repo <url> [--version <constraint>] [--install-deps]
baler install --path <dir-or-tar.gz> [--install-deps]
baler install --git <url> [--branch <ref> | --tag <ref> | --rev <ref>] [--module-dir <dir>] [--install-deps]
# Always look up at tarball source only
baler install --url <tarball> [--install-deps]

Use exactly one of <name>, --path, --git, or --url to say where to install from. Using more than one, or none, is an error.

Form What it does
<name> (positional) A bare module name for a registry lookup, nothing else. Never guessed as a path, mirrors cargo install <crate>/pip install <pkg> exactly. Needs --repo or it errors, telling you to use --repo, --path, --git, or --url instead.
--path <dir-or-tar.gz> A directory containing baler.toml, or a .tar.gz produced by baler bundle. The only way to install from a local source, mirrors cargo install --path.
--git <url> A GitHub repository URL, e.g. https://github.com/user/repo. Fetches through GitHub’s tarball API, not generic git, so a GitLab or self-hosted URL won’t work despite the flag’s name. Add --branch, --tag, or --rev to pin a ref (pick one), and --module-dir <dir> if the repo holds more than one module.
--url <tarball> Installs directly from an already-bundled archive URL, such as a GitHub Release asset from baler bundle. Must end in .tar.gz, checked up front.
--repo <url> Looks up <name> in a module registry at <url>. Registries aren’t built yet, so this currently just fails with a clear “not implemented” error. Add --version <constraint> once registries exist.

The extra parameter, --install-deps, downloads and installs the module’s dependencies, whether CRAN-style R packages (they are linked from CRAN by default) or box-bale modules. Without it, {baler} still shows what it would install, but doesn’t fetch anything. Installing a module that’s already installed under the same name replaces the old copy.

Installing via --git obviously needs internet access to reach GitHub, and those requests aren’t authenticated. If you’re installing several --git sources in a short window, for example in a CI pipeline, you would run into GitHub’s rate limit. Right now that shows up as a generic HTTP error rather than a message that clearly says “rate limited,” so if an install fails for no obvious reason, that’s worth checking.

Lock and lockfile

This is inspired by uv.lock and Cargo.lock. As long as the dependencies are declared on baler.toml, the command you’ll use, baler lock, will resolve everything a project depends on, both R packages and other modules, and writes the result to baler.lock so future installs use the exact same versions instead of re-resolving from scratch. This doesn’t install anything itself, it just pins what would be installed. Here’s the anatomy of baler lock command:

baler lock <path> [--update] [--with-rver] [--remove]
Argument Description
<path> The project’s root directory.
--update Ignore any baler.lock that already exists and re-resolve every dependency from scratch, instead of reusing what’s already pinned. Use this when you want to pick up newer versions.
--with-rver Also record the R version you currently have on your system inside baler.lock. This is informational only, it’s never enforced or checked against on install, just a record of what produced the lock.
--remove Delete baler.lock instead of writing one. Safe to run any time, if there’s no lockfile, {baler} just resolves dependencies fresh each time, same as a project that never had one.
Note

The positional form isn’t a fourth way to point at something — it always collapses to one of the three rows above (a sniffed local path, gh:user/repo, or a bare name for --repo). Note that gh:user/repo can’t take --branch, --tag, --rev, or --module-dir; those need the explicit --git <url> flag instead.

Uninstall a module

The bundle part gives you an archive, under .tar.gz format, of {box}-{baler} module. Then we have the “installation” part that archives the module and placed on the path where ~/.baler/modules lived. {baler} also has an option to uninstall a previously installed module, through baler remove. Here’s the following anatomy:

baler remove <name> [--force]
Argument Description
<name> The name of an installed module to remove.
--force Skip the [y/N] confirmation prompt and remove it immediately.

Fails if no module with that name is currently installed, there’s nothing to accidentally remove twice.