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 initorcargo 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:
baler.tomlREADME.md- 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_nativeThe 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 addsREADME.mdand an example module, in a folder named after the module. This is the same layout as a new project, only in place.--nativeand--backendapply here.Otherwise, when it is not empty, i.e. you already have the source code files and/or anything, it writes
baler.tomland nothing else. Your files are left as they are, and no example module or README is added.--nativeis 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.
You are in an empty directory called
weather/. Runningbaler initgives you a module namedweather, plusREADME.mdand an example module inweather/weather/. The same goes for a directory calledtidyverse-module/, where the module is namedtidyverse-module.You are in an existing
weather/project whose source code is inweather-module/:└── weather/ ├── LICENSE.md └── weather-module/ ├── __init__.r └── submodule/ ├── __init__.r └── source.rThe module is named
weather, but the source isn’t in a folder with that name, so pointbaler initat it:baler init --src weather-modulebaler.tomlis the only new file, and it records the source directory assrc = "weather-module".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 = trueis declared under the[compiled-code]frombaler.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 = trueis declared under the[compiled-code]frombaler.toml, the native source code will be kept on the archives. This is only valid together withbinary = 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. |
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.