smart-command-runner-rs

Run grouped shell commands from a TOML file with blocks, confirmation modes, and logging

Dry-run by default — nothing runs unless you pass --yes, --interactive-block, or --interactive-command.


cmdrun reads a TOML file with named blocks of shell commands and runs them. Blocks can be selected individually, all at once, or with per-block / per-command confirmation.

Why this tool: when setting up a fresh Linux install, you usually have a list of commands to run — sometimes dozens, sometimes hundreds. Typing them by hand is slow and error-prone. Shell scripts work, but you lose named blocks (run just "the base part" or "the Manjaro part"), per-block / per-command confirmation for the first dry run, and logs (what ran, what failed, how long it took). cmdrun solves this with a single TOML file reused across distributions without duplication.

Origin: This project is a revival of an earlier tool called Commandoro (2018), which performed a similar job: read a declarative file of grouped shell commands and run them. The original repository was closed and the project was abandoned. cmdrun is a rewrite from scratch in Rust, with a stricter data model (atomic commands, independent blocks, explicit ordering on the command line) and proper logging.

The tool operates on two levels of granularity.

Command — a single shell invocation. One command performs one action:

"sudo apt update -y"

Block — a named group of independent commands:

[base]
commands = [
    "sudo apt update -y",
    "sudo apt install -y git curl wget",
]

Rules:

  1. Each command is atomic. If two actions depend on each other, they are one command, not two. Combine: "git clone ... ~/y && cd ~/y && make install".
  2. Commands within a block are independent. A failure of one command does not affect the others. Execution continues.
  3. Block dependencies are declared on the command line, not inside the file: cmdrun --file setup.toml --run base,manjaro --yes. The TOML file is declarative. No depends_on, no inheritance, no macros.
  4. Command failures are collected, not fatal. A failed command does not stop the block, the file, or the process. All failures are reported at the end, and the process exit code is 0 unless a configuration error occurred.

  • TOML file with named blocks of commands
  • Run all blocks or a specific subset (--run base,manjaro)
  • --list to see blocks without running anything
  • Two execution modes: dry-run (default) and execute
  • Three confirmation modes: --yes, --interactive-block, --interactive-command (last two can be combined)
  • Per-command output with exit status and timing
  • Log file with timestamps (default: ~/.local/share/cmdrun/cmdrun.log)
  • --no-log to disable logging
  • Non-zero exit codes of failed commands are recorded in the log
  • Continues on errors — reports them at the end

Requires Rust 1.70 or newer.

Build from source:

git clone https://github.com/smartlegionlab/smart-command-runner-rs
cd smart-command-runner-rs
cargo build --release

Binary: target/release/cmdrun

Install for system-wide use (Linux):

mkdir -p ~/.local/bin
ln -sf "$PWD/target/release/cmdrun" ~/.local/bin/cmdrun

If ~/.local/bin is not in your PATH, add to ~/.bashrc:

export PATH="$HOME/.local/bin:$PATH"
source ~/.bashrc

A TOML file with one or more named blocks. Each block has:

  • description — optional string.
  • commands — required array of strings. Each string is one atomic command.
[base]
description = "Common setup for all Linux systems"
commands = [
    "sudo apt update -y",
    "sudo apt install -y git curl wget htop neovim",
    "mkdir -p ~/.local/bin",
]

[ubuntu]
description = "Ubuntu-specific packages"
commands = [
    "sudo apt install -y build-essential pkg-config libssl-dev",
]

[manjaro]
description = "Manjaro-specific packages"
commands = [
    "sudo pacman -Syu --noconfirm",
    "sudo pacman -S --noconfirm base-devel openssl",
]

[dev-tools]
description = "Cross-distro dev tools"
commands = [
    "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y",
    "rustup component add clippy rustfmt",
]

Blocks run in this order: the order passed to --run, or — if --run is omitted — alphabetical order of block names.

No depends_on, no inheritance, no macros. If block B needs block A, pass both: --run a,b. Explicit is better than magic.

cmdrun --file <FILE> [OPTIONS]

Options:

OptionDescriptionDefault
-f, --file <PATH>TOML file with blocks (required)—
-r, --run <LIST>Run only these blocks, in this order (comma-separated)all blocks alphabetically
--listShow blocks in the file and exit—
-y, --yesRun everything without askingfalse
-i, --interactive-blockAsk confirmation per blockfalse
-c, --interactive-commandAsk confirmation per commandfalse
--dry-runForce dry-run even with --yesdefault without -y/-i/-c
--log <PATH>Log file path~/.local/share/cmdrun/cmdrun.log
--no-logDo not write a log filefalse
--sh <PATH>Shell used to run each command (with -c)sh

Modes:

Command lineBehaviour
cmdrun --file XDry-run: show plan, execute nothing
cmdrun --file X --yesExecute everything, no questions
cmdrun --file X -iAsk confirmation per block
cmdrun --file X -cAsk confirmation per command
cmdrun --file X -i -cAsk per block, then per command inside each block
cmdrun --file X --yes --dry-runStill dry-run
cmdrun --file X -i -yError (conflict)
cmdrun --file X -c -yError (conflict)

Interactive prompt — per block:

Block [base] — Common setup for all Linux systems
  Commands: 3
Run this block? [y/N/a/q]

Interactive prompt — per command:

  [1/3] [base] sudo apt update -y? [y/N/a/q]

Answers: y — run this one · n or Enter — skip · a — run all remaining without asking · q — quit.

Examples:

# List blocks
cmdrun --file linux-setup.toml --list

# Dry-run (default — nothing is executed)
cmdrun --file linux-setup.toml

# Run everything without questions
cmdrun --file linux-setup.toml --yes

# Run only the common setup, then Manjaro-specific
cmdrun --file linux-setup.toml --run base,manjaro --yes

# First run — review every block, then every command
cmdrun --file linux-setup.toml -i -c

# Custom log path
cmdrun --file linux-setup.toml --yes --log ~/manjaro-setup.log

# No log file
cmdrun --file linux-setup.toml --yes --no-log

Default path: ~/.local/share/cmdrun/cmdrun.log. The file is appended to on each run.

[2026-09-28T10:23:45Z] === cmdrun START file=linux-setup.toml blocks=base,manjaro
[2026-09-28T10:23:45Z] [base] [1/2] RUN: sudo apt update -y
[2026-09-28T10:23:47Z] [base] [1/2] OK (2.10s)
[2026-09-28T10:23:47Z] [base] [2/2] RUN: sudo apt install -y git curl wget htop neovim
[2026-09-28T10:23:52Z] [base] [2/2] OK (4.71s)
[2026-09-28T10:23:52Z] [base] BLOCK DONE (6.81s)
[2026-09-28T10:23:52Z] [manjaro] [1/2] RUN: sudo pacman -Syu --noconfirm
[2026-09-28T10:23:59Z] [manjaro] [1/2] OK (7.12s)
[2026-09-28T10:23:59Z] [manjaro] [2/2] RUN: some-missing-command
[2026-09-28T10:23:59Z] [manjaro] [2/2] FAIL (0.00s): exit 127: sh: some-missing-command: not found
[2026-09-28T10:23:59Z] [manjaro] BLOCK DONE (7.12s)
[2026-09-28T10:23:59Z] === cmdrun END run=3 skipped=0 failed=1

Successful commands are logged as OK (Ns). Failed commands are logged as FAIL (Ns): exit N: <output>. Non-zero exit codes are preserved. A failed command does not stop the block.

  • 0 — success (even if some commands failed; check the summary)
  • 1 — invalid arguments, file missing, TTY required but missing, or block name not found

cargo test

Runs unit tests (in src/main.rs) and integration tests (in tests/integration.rs).

By using this software, you agree to the full disclaimer terms.

Software provided "AS IS" without warranty. You assume all risks.

Full legal disclaimer: See DISCLAIMER.md

License: BSD 3-Clause License