Python

Verified against Claude Code · 2026-07-23

Turn a one-off Python script into a proper CLI tool

A prompt that converts a script with hardcoded values or manual sys.argv indexing into a Typer or Click CLI with real help text, validation, safe defaults for destructive actions, and an installable entry point.

Claude CodeChatGPT (GPT-5.1)GitHub Copilot ChatCursor 2.14 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

Convert the script described below into a real command-line tool — not a script with sys.argv indexing, and not one that still has hardcoded values a user has to edit before running it.

CURRENT SCRIPT
A script that reads a folder path and a threshold hardcoded at the top, then deletes log files older than the threshold.

FRAMEWORK
Use Typer. If it's Typer, define arguments as type-hinted function parameters — that's Typer's actual mechanism — rather than manually building a parser. If it's Click, use the @click.command() and @click.option/@click.argument decorators. If it's argparse, use argparse.ArgumentParser with explicit type=, required=, and help= on every argument.

COMMANDS NEEDED
single command: clean, with options for folder, days-old threshold, and file extension filter

DESTRUCTIVE BEHAVIOR
Permanently deletes matched log files from disk; no recycle bin or backup.

REQUIREMENTS
1. Every option has a help string a stranger could act on without reading the source — "path to clean" not "the folder."
2. Every option with a sensible default gets one; every option that doesn't must be required and say so, rather than silently defaulting to None and failing three lines later with an unrelated AttributeError.
3. Validate inputs at the CLI boundary (file exists, path is a directory not a file, number is in range) and exit with a clear message and a non-zero exit code — never a raw traceback as the user-facing output.
4. Use meaningful exit codes: 0 for success, and distinct non-zero codes for distinct failure classes if there's more than one, documented in a comment.
5. If Permanently deletes matched log files from disk; no recycle bin or backup. describes anything that deletes, overwrites, or sends data, add a --dry-run flag that reports exactly what would happen, and default the destructive path to off unless --yes or --force is explicitly passed — never make the default invocation the destructive one.
6. Support --help correctly on both the top-level command and every subcommand named in single command: clean, with options for folder, days-old threshold, and file extension filter — a user should be able to discover every option without reading source code or guessing at flag names from a README that may be out of date.
7. If the tool reads or writes any path, resolve it relative to the current working directory the way a shell user expects, and say explicitly whether relative paths are resolved against the invocation directory or some other base — a script silently resolving paths against its own install location instead of where the user actually ran it from is a common and confusing surprise.
8. Print user-facing output to stdout and errors to stderr, not both mixed on stdout — this is what lets the tool be piped and scripted correctly by anything downstream that expects to separate normal output from failures.

OUTPUT FORMAT
1. The CLI entry point code.
2. The exact pyproject.toml [project.scripts] entry needed to install it as a real command.
3. Three example invocations: one happy path, one that fails validation, one using --dry-run if applicable.
4. Confirmation that --help produces useful output for the top-level command and every subcommand, and where stdout versus stderr is used.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

Naming the exact mechanism for the chosen framework — Typer's type-hinted parameters versus Click's decorator-based options versus argparse's explicit ArgumentParser calls — stops the model from defaulting to whichever pattern shows up most in its training data regardless of which library was actually requested, a common mismatch when a bare prompt just says "use Typer" with no further detail and gets back a hand-rolled parser instead. Requiring a --dry-run flag and an explicit opt-in for destructive behavior is a real safety practice lifted from how production CLI tools are actually built, and it's the detail a plain "turn this into a CLI" request reliably skips — the destructive_behavior field forces that risk to be named up front rather than discovered the first time someone runs the new tool with the same instinctive confidence they had in the old hardcoded script and it does something the old script never could at that scale. Requiring the pyproject.toml [project.scripts] entry, not just the code, is what makes the result an installable command a user runs by name from any directory, rather than something still invoked as python script.py with a longer argument list — a CLI tool that only works when you remember its absolute path has not actually replaced the script it was meant to replace. The requirement that every optional-looking argument either has a real default or is explicitly required also prevents a specific, common failure: a value that's technically accepted as None by the function signature but crashes several lines into execution with an unrelated-looking error, which is a strictly worse experience than the CLI simply refusing to start with a clear message about the missing argument. Separating stdout from stderr, and being explicit about which working directory relative paths resolve against, matter for the same reason the pyproject.toml entry point does: a script that only ever gets run interactively by the person who wrote it can get away with mixing output streams and assuming a fixed working directory, but a tool that's actually installed and reused stops being scriptable — pipeable into another command, callable from a cron job with a different working directory, checked for success by an exit code alone — the moment either of those assumptions turns out to be wrong.

What you get back

import typer from pathlib import Path from datetime import datetime, timedelta app = typer.Typer() @app.command() def clean( folder: Path = typer.Argument(..., exists=True, file_okay=False, help="Folder to clean."), days_old: int = typer.Option(30, help="Delete files older than this many days."), extension: str = typer.Option(".log", help="Only delete files with this extension."), dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting."), yes: bool = typer.Option(False, "--yes", help="Actually delete files (required unless --dry-run)."), ) -> None: cutoff = datetime.now() - timedelta(days=days_old) targets = [f for f in folder.glob(f"*{extension}") if datetime.fromtimestamp(f.stat().st_mtime) < cutoff] if dry_run or not yes: typer.echo(f"Would delete {len(targets)} files. Pass --yes to actually delete.") raise typer.Exit(code=0) for f in targets: f.unlink() typer.echo(f"Deleted {len(targets)} files.") [project.scripts] logclean = "logclean.cli:app"

Verified against

Claude Code Sonnet 4.6 · 2026-07-23

ChatGPT GPT-5.1 · 2026-07-24

Changelog

  • 2026-07-24 Initial publish, verified against Claude Code (Sonnet 4.6) and ChatGPT (GPT-5.1) on Typer 0.13.

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
All Python prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY