Verified against ChatGPT · 2026-08-10
Scaffold a Python CLI tool whose flags, config file, and environment variables don't silently fight each other
Builds a Click-based CLI with subcommands, a clearly defined config-precedence order, and exit codes a shell script can actually branch on — not just a toy argparse demo.
The prompt
Ready to copy — highlighted parts are example details you can swap.
Build a Python CLI tool using Click. This needs to work as a real command-line tool other people or scripts will invoke, not a demo script — that means config precedence, exit codes, and error messages all have to be deliberate, not accidental. WHAT THE TOOL DOES Syncs a local directory of markdown files to a remote S3 bucket, skipping files that haven't changed since the last sync. SUBCOMMANDS NEEDED sync (does the actual upload), status (shows what would change without uploading), init (writes a starter config file) CONFIG SOURCES bucket name and AWS profile can come from a .syncrc file or env vars; --dry-run and --verbose are flag-only. WHO/WHAT WILL INVOKE THIS Runs both interactively by engineers and inside a nightly CI job that needs to fail the pipeline on a real error but not on 'nothing to sync'. RULES Define one explicit precedence order for settings that can come from more than one of: a config file, environment variables, and CLI flags — state the order plainly (e.g. CLI flag overrides env var overrides config file overrides built-in default) and implement it that way consistently across every setting, rather than letting each option's implementation drift independently, which is how tools end up with one flag that overrides the config file and another that silently doesn't. Every subcommand needs a docstring Click will surface in `--help` that states what it does and, for anything destructive, what it will NOT undo. Distinguish exit codes deliberately: 0 for success, a distinct non-zero code for "ran correctly but found nothing to do / a validation failure the user caused" versus a different code for "an unexpected internal error" — because the invocation context above determines whether a calling script needs to branch on that distinction, and collapsing everything to exit code 1 makes that impossible. Validate all user input at the CLI boundary with clear error messages that name which argument was wrong and why, before any side-effecting logic runs — never let a malformed flag surface as a stack trace. WHAT NOT TO DO Do not use `print()` for anything other than the tool's actual output going to stdout; route diagnostic/progress messages to stderr via Click's `echo(..., err=True)` so stdout stays pipeable. Do not silently swallow an exception to "keep the CLI clean" — a caught exception must either be handled with a specific recovery action or re-raised with added context, never passed and ignored. OUTPUT FORMAT 1. The full Click CLI code (entry point, subcommands, config loading in precedence order). 2. A short table: exit code -> meaning -> when it happens. 3. One example `--help` output for the main command. 4. A one-paragraph note on how a calling script should check success/failure of this tool.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
Requiring one explicit, stated precedence order applied consistently across every configurable setting addresses a specific bug pattern in hand-rolled CLI tools: without an explicit rule, each setting tends to get its config-file/env-var/flag resolution logic written independently as the tool grows subcommand by subcommand, and inconsistent precedence between settings is invisible until a user's env var mysteriously overrides one flag but not another — asking for a single named rule up front, rather than letting each setting's precedence emerge from wherever the code happened to put it, is what actually prevents that drift. The exit-code table requirement matters specifically because the invocation context states this tool runs inside CI as well as interactively, and a CI pipeline can only make correct pass/fail decisions if "nothing to sync" and "the AWS credentials were invalid" produce genuinely different exit codes — a model asked generically to "build a CLI tool" defaults to exit 0 for success and exit 1 for everything else, which is exactly the collapse that makes automated pipelines either too permissive (masking real failures) or too strict (failing on a no-op run). Routing diagnostics to stderr via Click's `err=True` rather than plain `print()` is a mechanical requirement, not a style preference — a calling script that captures this tool's stdout to parse or log its actual output will silently ingest progress noise as data if diagnostics aren't kept off stdout, which is a common and hard-to-debug failure in composed shell pipelines specifically because it doesn't crash, it just corrupts the downstream data quietly.
What you get back
Exit codes: 0 = synced successfully or nothing to do; 2 = user error (bad config, invalid flag combination) — CI should treat as a build config problem; 1 = unexpected internal failure (network error, S3 auth failure) — CI should retry or alert. `--help` shows: Usage: filesync [OPTIONS] COMMAND [ARGS]... Commands: init, status, sync. Config precedence: CLI flag > FILESYNC_* env var > .syncrc > built-in default.
Verified against
ChatGPT GPT-5.1 · 2026-08-10
Changelog
- 2026-08-10 — Initial publish, verified against ChatGPT GPT-5.1.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
