An opinionated, batteries-included development CLI operator.
brew install rsvalerio/tap/ops# Links a prebuilt libduckdb instead of compiling it (needed for any
# linking build; cargo check/clippy work without it)
eval "$(scripts/fetch-duckdb.sh)"
cargo install --path crates/cli# Initialize config for your project (auto-detects stack)
ops init
# Run a command
ops build
# Run static checks (fmt, check, clippy, build)
ops verify
# Run tests and quality checks
ops qa
# Add a new command interactively
ops new-command "cargo fmt --check"Create a .ops.toml file in your project root (or run ops init):
[output]
theme = "classic" # "classic" (default) or "compact"
columns = 80 # line width for step lines
show_error_detail = true # show error details below failed steps
[commands.build]
program = "cargo"
args = ["build", "--all-targets"]
[commands.test]
program = "cargo"
args = ["test"]
[commands.verify]
commands = ["fmt", "check", "clippy", "build"]
parallel = true
fail_fast = true
[commands.qa]
commands = ["test", "deps"]
parallel = true
fail_fast = trueConfig is merged in order (later overrides earlier): built-in defaults → global config (~/.config/ops/config.toml) → local .ops.toml → .ops.d/*.toml fragments (sorted by filename) → OPS__* environment variables. When run inside a project with a detected stack (e.g. Rust), ops init pre-fills stack-specific commands.
A command with a commands = [...] list is a group (composite). Groups may
reference other groups, and ops expands the whole tree into a single flat plan
that is scheduled as one unit.
Because the plan is scheduled as one unit, every group in a plan must declare
the same parallel and the same fail_fast. A tree that disagrees with
itself is rejected with an error naming both groups:
[commands.lint]
commands = ["ruff", "black"]
parallel = true
[commands.verify]
commands = ["fmt", "lint"]
parallel = false # error: conflicts with lint.parallel = true$ ops verify
error: conflicting `parallel` in the plan for `verify`: `verify` sets parallel = false,
but `lint` sets parallel = trueThis is deliberate. The flags used to be OR-folded across the tree, so a single
parallel = true group silently promoted the entire plan to parallel while its
parent still read parallel = false — which meant formatters could run
concurrently with the checkers reading the same files. Rejecting the config
makes that loud rather than intermittent.
To fix, make the flags agree — either set lint.parallel = false, or set
verify.parallel = true if the whole plan really should run concurrently.
Note that this applies within one plan. Naming several commands on one
invocation (ops run verify qa) expands each independently, so they may differ.
Expressing "run these groups in order, but let the steps inside one group run together" is not supported today; it needs per-group scheduling boundaries.
| Command | Description |
|---|---|
ops <name> |
Run a configured command or command group |
ops init |
Create .ops.toml (minimal by default; --force to overwrite; --output/--themes/--commands add those sections, with stack-detected commands under --commands) |
ops new-command |
Add a new command from a command line string |
ops import-makefile |
Import Makefile targets as .ops.toml commands (interactive picker) |
ops theme list|select |
List or select output themes |
ops extension list|show |
List compiled-in extensions |
ops about [setup|code|loc|coverage|dependencies|crates|modules] |
Project identity card and subpages (--refresh re-collects) |
ops run-before-commit [install] |
Pre-commit hook runner (--changed-only skips when nothing is staged) |
ops run-before-push [install] |
Pre-push hook runner (skips a delete-only or empty push) |
ops sec |
Security scans via Trivy — secrets always, vulnerability/misconfig auto-selected by file types (--skip/--force to override). Fails closed: non-zero on findings, on a scan timeout, and when --skip leaves no scan to run. Each scan is bounded by a 10-minute timeout, overridable with OPS_SEC_TIMEOUT_SECS=<seconds> |
ops trailing-whitespace (tw) |
Strip trailing whitespace in place; non-zero when files changed (pre-commit contract) |
ops end-of-file-fixer (eof) |
Ensure files end with exactly one newline; non-zero when files changed |
ops check-json / check-yaml |
Verify every JSON/YAML file parses (--tracked limits to git files; --allow-json5 for JSON5) |
Global flags: --dry-run (preview the resolved plan), --verbose (full stderr on
failure), --tap <file> (capture raw output), --raw (inherit child stdio, no ops output).
Hook escape hatches: set SKIP_OPS_RUN_BEFORE_PUSH (or SKIP_OPS_RUN_BEFORE_COMMIT)
to 1, true, yes or on — case-insensitive; anything else means "do not skip" —
to let a push or commit through without running the configured hook commands.
| Command | Available on |
|---|---|
ops deps |
Rust |
ops plans |
Terraform (plan summary tables) |
ops about coverage / dependencies |
Rust |
ops about loc |
Rust |
ops about crates / modules |
Rust, Go |
Every supported stack ships the same 7-command contract via ops init --commands.
A ✓ means the command is active by default; * means it's emitted commented-out
as a suggestion you can uncomment and adjust.
| Command | Rust | Vite | Node | Go | Python | TF | Ansible | Java-M | Java-G |
|---|---|---|---|---|---|---|---|---|---|
fmt |
✓ (cargo fmt) | * (bunx prettier) | * (prettier) | ✓ (go fmt) | ✓ (ruff format, key format) |
✓ (tf fmt) | * (ansible-lint --fix) | * (spotless) | * (spotless) |
lint |
✓ (cargo clippy, key clippy) |
✓ (bunx eslint) | ✓ (npm run lint) | ✓ (go vet, key vet) |
✓ (ruff check) | * (tflint) | ✓ (ansible-lint) | * (spotless/checkstyle) | * (spotless/checkstyle) |
build |
✓ | ✓ (bunx vite build) | ✓ | ✓ | * (python -m build) | * (terraform plan) | * (galaxy build) | ✓ | ✓ |
test |
✓ | ✓ (bunx vitest run) | ✓ | ✓ | ✓ (pytest) | * (terraform test) | * (molecule test) | ✓ | ✓ |
clean |
✓ (cargo clean) | * (rm node_modules dist) | * (rm node_modules dist) | ✓ (go clean) | * (rm caches) | * (rm .terraform) | * (rm .ansible) | ✓ | ✓ |
verify |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
qa |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
The Vite stack also ships a typecheck command (bunx tsc -b --noEmit) wired into its verify,
and is detected before Node via vite.config.* so Vite/TypeScript projects get type-aware defaults.
The Rust stack default goes beyond the contract: it also ships next / next-ignored
(cargo-nextest; nextest does not run doctests), test-doc for those doctests, and a
qa-next composite (alias qax) that runs the test legs through nextest. The Rust qa
runs deps, test, test-ignored, test-doc, and sec — sec requires the
Trivy CLI on PATH.
Commented suggestions show up verbatim when you run ops init --commands, so you can
opt in by uncommenting, or remap to the tool your project actually uses.
Rust is the reference implementation; the other stacks are data providers compiled
into the same binary via ops-extension. Parity gaps are feature scope, not
separate language rewrites.
Stack flavors currently shipped:
- Rust — cargo (workspaces supported)
- Go — go modules /
go.work - Node — package.json (pnpm/yarn/npm workspaces)
- Java-Maven — pom.xml with
<modules>multi-module support - Java-Gradle — Gradle with
settings.gradle(.kts)subprojects - Python + uv —
pyproject.toml(PEP 621) with uv workspace members from[tool.uv.workspace]/uv.lock. A generic Python flavor (poetry, pip/setuptools, pdm, etc.) is not yet implemented.
| Area | Rust | Go | Java-M | Java-G | Node | Python+uv |
|---|---|---|---|---|---|---|
CLI core (init, theme, extension, hooks) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 7-command contract (fmt/lint/build/test/clean/verify/qa) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
project_identity provider |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Module count on identity card | ✓ | ✓ | ✓ (modules) | ✓ (subprojects) | ✓ | ✓ |
project_units provider (about modules subpage) |
✓ | ✓ | ✗ | ✗ | ✓ | ✓ (uv workspace only) |
about code (tokei LOC, feature-gated) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
about loc (production/test/example split) |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
about coverage (cargo llvm-cov) |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
about dependencies / ops deps |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
Ranked by closeness to Rust parity: Node and Python+uv (identity + units + baseline CLI), Go (~90%, weaker units provider), Java-Maven / Java-Gradle (identity + module counts, but no project_units provider yet for the about modules subpage).
Rust-only extensions: deps, cargo-toml, cargo-update, metadata, test-coverage, rust-loc. about code is stack-agnostic (tokei scans any language) and only gated by the compile-time tokei feature on the ops binary; about coverage and about dependencies are Rust-only because their providers shell out to cargo llvm-cov / cargo metadata.
about coverage (and about --refresh, which re-collects coverage data) requires external tools that ops does not install for you:
cargo install cargo-llvm-cov
rustup component add llvm-tools-previewWhen they are missing, the coverage warning/error includes these same install commands as a hint.
about code and about loc answer different questions and are not expected to agree: tokei reports every language but has no model for test versus production code, while rust-loc parses only .rs files and splits #[cfg(test)] blocks out of the file that contains them, counting doc comments separately from ordinary ones. On a non-Rust workspace ops about loc prints No Rust LOC data available.
- Generic Python stack (non-uv: poetry, pip/setuptools, pdm, hatch)
- Java
project_unitsprovider — module counts already surface on the main about card (Maven modules, Gradle subprojects), butops about modulescan't list them per-unit until aproject_unitsprovider ships
- Zero config — works out of the box with sensible defaults;
ops initand friends scaffold the rest - Declarative commands — define commands and command groups in TOML
- Themed output — step lines with timing; switch between themes easily
- Extension architecture — compile-time extensions; build your own ops
- Parallel execution — run command groups concurrently with
parallel = true
This project uses Conventional Commits. Only feat and fix commits trigger a release.
git commit -m "feat: add new feature"
git commit -m "fix: resolve bug"See docs/releasing.md for the full release workflow.
- Releasing — automated releases, conventional commits, Homebrew tap
- Visual Components — step icons, error boxes, theme comparison
Apache-2.0