← Back

Principles for a better CLI

Seven principles for building a CLI that works well — for the data engineer running it in a terminal, and for the agent running it in a pipeline.

01

Design for both humans and automation.

A CLI isn't just used by a person at a terminal anymore. It's also run by scripts, CI jobs, and other tools. The same commands, flags, and outputs should work in both cases. If something only works interactively or only makes sense to a human, it becomes harder to test and automate. Designing for both use cases usually improves the tool overall.

02

Prefer flags over prompts.

If a command needs input, that input should be passable as a flag. Prompts can still exist, but they shouldn't be required. In practice, there are three ways to provide values: flags, environment variables, or prompts. Once the value is provided, execution should be the same.

From today's session

$ montecarlo configure
Key ID: █
Secret: █
Error — mcd_token received should
have 56 length but received 112

The configure command has no --id or --token flag. The only path is interactive. When the paste failed, the secret ran as a shell command and became visible in terminal history.

What it could look like

$ montecarlo configure \
--id KEY --token TOKEN
✓ Authenticated

# or via env vars:
MCD_ID=KEY MCD_TOKEN=TOKEN \
montecarlo monitors list

# interactive prompt still available
# but no longer the only path

03

Make the command structure reflect the product.

The top-level commands should show what the tool manages. Use nouns at the top level and verbs for actions beneath them. Mixing everything together or adding commands without structure makes the CLI harder to understand as it grows.

From today's session

configure export import validate
# verbs at the top level, mixed with nouns

add-airflow-callbacks
add-athena
add-bigquery
add-databricks
add-snowflake
add-spark-binary-mode
add-spark-http-mode
# 50+ commands under integrations
# one per integration, grows forever

Running montecarlo incidents --help returns "No such command." Alerts exist only as a bulk export. The most important things MC does are not queryable from the CLI.

What it could look like

monitors List, get, create, delete.
incidents View active incidents.
assets Browse data assets.
integrations Manage connections.
agents Manage MC agents.
auth Configure credentials.

# integrations add --type snowflake
# new integrations don't add new commands

04

Make output usable by both people and programs.

Output should be readable and structured. That usually means separating data from presentation internally, so commands can return structured data and format it as needed. This makes it easier to support formats like JSON without extra work later.

From today's session

$ montecarlo monitors list

| 8df0d2ce | STATS | benya_ns |
| Monitor for anomalous changes |
| in f_sales row count |

There are more monitors available.
Increase the limit to view them.

Rows wrap mid-row at normal terminal widths. There is no status column. The truncation message doesn't mention --limit. There is no --json flag anywhere in the CLI.

What it could look like

$ montecarlo monitors list

NAME TYPE STATUS
──────────────────────────────────
orders.daily_revenue STATS FAILING
users.signup_rate STATS FAILING
inventory.stock TABLE OK

Showing 3 of 247 — use --limit 247

05

Make errors actionable.

Error messages should explain what went wrong and what to do next. When possible, point to the exact command or fix. Use distinct exit codes so scripts can tell different failure cases apart.

From today's session

Failed to find configuration for
'default'. Please setup using
'configure' first
Aborted!

log_error — https://api.getmontecarlo
.com/graphql: 403 Client Error:
Forbidden for url: https://api.
getmontecarlo.com/graphql

The first error tells you to run configure but not what configure needs or where to get a key. The second exposes an internal GraphQL URL. Both exit with code 1 — the same code used for every other failure.

What it could look like

Error: not authenticated.
Run: montecarlo auth \
--id KEY --token TOKEN
Get a key: app.getmontecarlo.com/
settings/api-keys

Error: invalid credentials.
Your key may have expired.
Generate a new one at the same URL.
# exit code 3 = auth failure
# scripts can branch on this

06

Keep behavior visible.

Users should be able to see how the tool is configured and what it's doing. Things like credential sources, active accounts, and config precedence shouldn't be hidden or require digging through files or source code.

07

Make the CLI self-describing.

Commands, flags, and concepts should be clearly explained in the built-in help. A user should be able to understand how to use the tool without leaving the terminal. This also helps automated systems that rely on help text to figure out how the CLI works.

From today's session

convert-to-mac
"Convert monitors from UI to monitors..."

export-as-latest
"Export monitors in a monitor as code namespace..."

run
"Run a circuit breaker monitor and wait for the..."

Every description in the monitors group trails off mid-sentence. "mac" means monitors-as-code, not macOS — you would not know that from the help text.

What it could look like

export-as-code
"Export monitors to a YAML file
for use with monitors as code.
Outputs to ./montecarlo.yml"

run
"Run a circuit breaker monitor
and wait for it to complete.
Exits 0 on pass, 1 on fail."