The commands
Every subcommand, the flags it takes, the exit code it returns and what it prints to a terminal or a pipe.
Every command finds your project by walking up from the current directory, so all of them work from any subdirectory.
| Command | Options |
|---|---|
molto new <name> |
— |
molto init |
— |
molto build |
-p/--profile, --refresh-toolchain, -j/--jobs |
molto run [-- args] |
-p/--profile, --refresh-toolchain, -j/--jobs |
molto test |
-p/--profile, --refresh-toolchain, -j/--jobs |
molto clean |
-a/--all |
molto fmt |
-c/--check, -d/--diff, --refresh-tools, --refresh-analysis, -j |
molto lint |
-p/--profile, -f/--format, --refresh-toolchain, --refresh-tools, --refresh-analysis, -j |
molto add <dep>[@<version>] |
--dev, --git, --path, --archive, --registry |
molto remove <dep> |
— |
molto metadata |
-o/--output, --include-dev |
molto login |
-r/--registry, -e/--email, -t/--token |
molto publish |
--recipe, -f/--file, --dry-run |
molto bench, molto migrate and molto update exist in the CLI and exit 5: the command is
recognised, the behaviour is not implemented. update stays that way on purpose —
molto add <name> is the upgrade, the way npm install is.
The parser accepts --opt value, --opt=value, -o value and boolean flags. Everything after
-- is forwarded to the program under molto run. --help and --version come for free.
Starting a project
molto new my_app # a new directory with a manifest, src/, include/, tests/ and a .gitignore
molto init # the same, in the directory you are already in
Neither ever overwrites a file that already exists, and neither initialises git.
Building and running
molto build # into build/debug/
molto build -p release # into build/release/
molto run -- --flag value # build, run, and forward everything after --
molto test # one binary per file under tests/, by default
molto run propagates the program’s own exit code verbatim, and reports 128 + N when it dies
from signal N. That is the one command whose exit code is not Molto’s.
-j <n> caps the worker pool; absent means every core. It is refused unless it is a count between
1 and 1024, and it is never recorded or fingerprinted — two builds differing only in -j produce
the same objects.
Output layout
| Path | Contents |
|---|---|
build/<profile>/<package> |
The executable |
build/<profile>/obj/src/**/*.c.o |
Intermediate objects |
build/<profile>/tests/<name> |
One binary per test file (per_file) |
build/<profile>/tests/<package>_tests |
The single test binary (single) |
.bin/wsdb |
Incremental state and the cached toolchain answer |
compile_commands.json |
The compilation database, at the project root |
Molto.lock |
The resolved dependency graph |
build/ and .bin/ belong to Molto and are gitignored. Molto.lock is generated and
committed. molto clean removes build/; molto clean --all also removes .bin/. Neither
touches ~/.molto.
compile_commands.json is written by a build, and the last one wins: molto build covers
src/ plus dependencies, molto test covers those and tests/, and -p release leaves a
database describing release. fmt and lint write none. It is written even when the build fails.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Build failure; fmt --check found unformatted files; lint produced an error |
| 2 | Invalid manifest or invalid configuration |
| 3 | Dependency failure |
| 4 | Usage error |
| 5 | The command exists but is not implemented |
A lint warning is reported and still exits 0. Only error severity fails the command.
What a build prints
Everything goes to stderr, so molto build > log still shows the build and molto run > out
captures only your program.
On a terminal it draws an inventory, then a live region, and takes the region away when the work stops:
◆ registry sqlite3 v3.50.3
◇ modules network
● project 62 files
○ cached 20 files
● project services/build_service.c
◆ sqlite3 btree.c
… and 5 more
████████░░░░░░░░ 22% 47/210
A dependency gets one line however many sources it has — a package is one piece of work.
Nothing already up to date is listed; it is counted on cached, so a no-op rebuild is three lines
and no region.
Redirect stderr — a pipe, a file, a CI job — and you get one line per source, no region, no
bar, and not one escape sequence. That is the full record, and it is what CI should keep.
NO_COLOR=1 removes the colour on a terminal but not the region, which is about motion. The
cursor is never hidden, so Ctrl-C leaves the terminal as it found it.
A failing unit is reported in a frame carrying the source line, a caret under the character the
tool named, and a footer saying whose code it was. A whole diagnostic is composed and handed to
the report in one call, so two workers can never interleave. A build that fails prints no
Finished line.
Environment variables
| Variable | Effect |
|---|---|
MOLTO_PICKUP |
Path to the pickup binary when it is not on the PATH |
C_COMPILER |
Forces the C driver — skips pickup and skips requires verification |
CPP_COMPILER |
Forces the C++ driver, same caveat |
MOLTO_CLANG_FORMAT |
Path to the formatter; bypasses resolution, not cached |
MOLTO_CLANG_TIDY |
Path to the linter; same |
MOLTO_CACHE |
Root of the shared dependency cache; ~/.molto/cache otherwise |
NO_COLOR |
Honoured by the test runner |
Molto prints a warning to stderr when C_COMPILER is used, because a compiler chosen by hand was
never checked against [target].requires.
Where pickup comes in
Molto does not choose a compiler. A manifest states the capabilities the code needs; pickup answers which local binary provides them, and the answer is cached in the workspace database — the question is asked once rather than on every build.
pickup list # every toolchain on this machine
pickup doctor # what stops this machine from building, and what fixes it
pickup install clang
pickup tools # the formatter and the linter, and where they are
If pickup resolve reports a runtime_dirs for a toolchain it installed, what Molto links
carries the -rpath in itself, so a binary keeps running wherever it goes.
Platform
POSIX only, deliberately: fork/exec, flock and sysconf are used directly, and CI runs Ubuntu.
Windows and macOS were promised for 0.2, moved to 0.4, and stay parked until the ecosystem is deep
enough on one platform to be worth porting to three.