Use when creating, editing, refactoring, or reviewing modularized Python CLI scripts managed by uv with pyproject.toml; scaffolding new script projects; adding commands or subcommands to Click-based CLIs; verifying CLI behavior adhoc; configuring hatchling builds or dependency groups; creating wrapper shell scripts for uv-managed projects. Triggers on phrases like "new python script", "add a CLI command", "scaffold a script project", "python CLI", "click command", or any work in a scripts/ directory containing pyproject.toml with hatchling. Do NOT use for single-file scripts, Jupyter notebooks, web applications, or Django/Flask projects.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Use when creating, editing, refactoring, or reviewing modularized Python CLI scripts managed by uv with pyproject.toml; scaffolding new script projects; adding commands or subcommands to Click-based CLIs; verifying CLI behavior adhoc; configuring hatchling builds or dependency groups; creating wrapper shell scripts for uv-managed projects. Triggers on phrases like "new python script", "add a CLI command", "scaffold a script project", "python CLI", "click command", or any work in a scripts/ directory containing pyproject.toml with hatchling. Do NOT use for single-file scripts, Jupyter notebooks, web applications, or Django/Flask projects.
Python Script Projects
Modularized Python CLI scripts: self-contained projects managed by uv, built with hatchling, Click
for command routing. The sole audience is LLMs; never humans.
Philosophy
These scripts are means to an end. They serve LLMs. Refactor mercilessly. Do not constrain changes
by scope, backward compatibility, or conservatism. A proper end result matters more than a minimal
diff. If touching a file reveals violations or suboptimal patterns, fix them regardless of whether
they relate to the original task.
Project Structure
project-name/
pyproject.toml
uv.lock # committed; deterministic installs
package_name/
__init__.py # __version__ = "0.1.0"
__main__.py # from package_name.cli import cli; cli()
cli.py # root Click group with auto-discovery
_click.py # HelpfulGroup class
_errors.py # die(), domain exceptions
command_a.py # exposes `cli` attribute (auto-discovered)
_helpers.py # underscore prefix = private, skipped by auto-discovery
subgroup/ # nested command group (subpackage)
__init__.py # defines group, imports subcommand modules
subcommand.py # attaches to parent group via decorator
[project]name = "project-name"version = "0.1.0"description = "One-line description of what this CLI does"requires-python = ">=3.13"dependencies = ["click>=8.1"]
[build-system]requires = ["hatchling"]
build-backend = "hatchling.build"[tool.hatch.build.targets.wheel]packages = ["package_name"]
Rules:
Click is always a dependency. No argparse. No exceptions.
Minimum-version pins only (>=X.Y), not ranges or exact pins
[tool.hatch.build.targets.wheel] packages MUST point to the package directory
Dev tools in [dependency-groups] dev (not [project.optional-dependencies]); omit the group
entirely when there are none
No test framework: no pytest dependency, no [tool.pytest.ini_options] (see Adhoc Verification)
Omit license, authors, URLs, classifiers
No [project.scripts]; use wrapper scripts (see Invocation)
Invocation
Projects are invoked via thin shell wrappers that use uv run --project:
#!/usr/bin/env bash# Project-scoped vars from the caller's repo (mise commonly exports UV_PYTHON)# would hijack this tool's interpreter. They arrive two ways: inherited through# the environment, and re-injected by mise's uv shim from the caller's cwd.execenv -u UV_PYTHON -u UV_PROJECT -u UV_PROJECT_ENVIRONMENT \
-u VIRTUAL_ENV -u PYTHONPATH -u PYTHONHOME MISE_NO_ENV=1 \
uv run --quiet \
--project "$(chezmoi source-path)/../scripts/project-name" \
-m package_name "$@"
For non-chezmoi repos, resolve relative to the wrapper itself:
The env prefix is mandatory. uv honors ambient UV_* regardless of --project, so a caller repo
pinning a Python older than the project's requires-python breaks the tool outright. env -u alone
is not enough when uv resolves to a mise shim: the shim reloads the caller's mise.toml and
re-injects [env] values, so MISE_NO_ENV=1 is what neutralizes it.
Click Patterns
Root Group with Auto-Discovery
Any module in the package exposing a cli attribute (click.Command or click.Group) is
registered automatically as a subcommand.
"""Root CLI group with auto-discovery of subcommand modules."""from __future__ import annotations
import importlib
import pkgutil
from pathlib import Path
import click
from package_name._click import HelpfulGroup
class_AutoGroup(HelpfulGroup):
"""Click group that auto-discovers subcommand modules.
Any module in the package that exposes a ``cli`` attribute
(a click.Group or click.Command) is registered as a subcommand.
Modules whose names start with ``_`` are skipped (private helpers).
"""def__init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._loaded = Falsedef_load_plugins(self):
ifself._loaded:
returnself._loaded = True
pkg_path = str(Path(__file__).parent)
for info in pkgutil.iter_modules([pkg_path]):
if info.name.startswith("_") or info.name == "cli":
continuetry:
mod = importlib.import_module(f"package_name.{info.name}")
except Exception:
continue
cmd = getattr(mod, "cli", None)
ifisinstance(cmd, click.Command):
self.add_command(cmd, info.name)
deflist_commands(self, ctx):
self._load_plugins()
returnsuper().list_commands(ctx)
defget_command(self, ctx, cmd_name):
self._load_plugins()
returnsuper().get_command(ctx, cmd_name)
@click.group(
cls=_AutoGroup,
context_settings={"help_option_names": ["-h", "--help"]},
)@click.version_option(
version=__import__("package_name").__version__, prog_name="project-name")defcli():
"""One-line description matching pyproject.toml."""
HelpfulGroup (_click.py, verbatim in every project)
"""Custom Click classes that show full help on usage errors."""from __future__ import annotations
import click
classHelpfulGroup(click.Group):
"""Click group that appends the failing command's help to usage errors."""definvoke(self, ctx: click.Context) -> None:
try:
returnsuper().invoke(ctx)
except click.UsageError as exc:
if exc.ctx isnotNone:
click.echo(exc.format_message(), err=True)
click.echo("", err=True)
click.echo(exc.ctx.get_help(), err=True)
else:
click.echo(exc.format_message(), err=True)
raise SystemExit(exc.exit_code) fromNone
Command Modules
Each command module exposes a cli attribute:
"""Brief description of what this command does."""from __future__ import annotations
import click
@click.command()@click.argument("target")@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")defcli(target: str, verbose: bool) -> None:
"""Verb-phrase describing the action."""
...
For command groups (subpackages), the __init__.py defines the group and imports subcommands:
from __future__ import annotations
import click
from package_name.subgroup import cli
@cli.command()@click.argument("repo")defsub_a(repo: str) -> None:
"""Verb-phrase describing this subcommand."""
...
__main__.py (exact pattern, no variation)
"""Entry point for `python -m package_name`."""from package_name.cli import cli
if __name__ == "__main__":
cli()
Error Handling
_errors.py
"""Error types and fatal exit helper."""from __future__ import annotations
import sys
from typing import NoReturn
classToolError(Exception):
"""Domain-specific error (e.g., API failure, invalid input)."""defdie(message: str) -> NoReturn:
"""Print error to stderr and exit."""print(f"error: {message}", file=sys.stderr)
sys.exit(1)
Name the exception class after the domain (FetchError, GhError, ApiError). One per project is
typical; add more only when callers need to distinguish failure modes.
Exception Flow
Raise domain exceptions from helpers; catch at command level with click.echo(..., err=True) +
sys.exit(1)
LLM consumption only. Token efficiency is the primary constraint.
click.echo() for all output. Never print() (except inside die()).
Errors: click.echo(..., err=True). Data: click.echo(...) to stdout.
Default format is prose. Short sentences, no filler.
NEVER JSON/YAML/tables unless a downstream tool requires machine-parseable input.
NEVER colors, bold, ANSI escapes, spinners, progress bars, box-drawing, emoji.
NEVER depend on rich, tabulate, colorama, tqdm, or similar.
Truncate long output: [truncated at N chars]
Help text and error messages: terse and informative, not friendly or decorative.
Configuration
Environment variables for secrets/host config. CLI args for per-invocation settings. Hard-coded
defaults. No config files. Validate env vars early:
"""Configuration from environment."""from __future__ import annotations
import os
from package_name._errors import die
defrequire_env(name: str) -> str:
"""Return env var value or die with clear message."""
value = os.environ.get(name)
ifnot value:
die(f"{name} is not set")
return value
Subprocess Wrappers
Typed helpers in private modules (_kubectl.py, _gh.py, etc.):
"""Subprocess wrapper for external-tool."""from __future__ import annotations
import shutil
import subprocess
from package_name._errors import ToolError, die
defcheck_deps() -> None:
"""Verify external tool is available. Called once at startup."""ifnot shutil.which("tool"):
die("tool not found; install it first")
defrun_tool(*args: str) -> str:
"""Run tool with args, return stdout. Raises ToolError on failure."""
result = subprocess.run(["tool", *args], capture_output=True, text=True)
if result.returncode != 0:
raise ToolError(result.stderr.strip())
return result.stdout
Call check_deps() from the root CLI group callback (the cli() function body in cli.py).
Code Style
from __future__ import annotations at the top of every module
Type hints on all function signatures
NoReturn for die() and similar
Docstrings: module-level (one line), class-level (brief), public functions (brief)
Private helpers: underscore-prefixed module names and function names
No if __name__ == "__main__" in modules other than __main__.py
Imports: stdlib, blank line, third-party, blank line, local (isort default)
Adhoc Verification
MUST NOT introduce pytest, unittest, or any test framework. No tests/ directory, no test files, no
fixtures, no conftest. These scripts are disposable LLM tooling; a maintained test suite costs more
than it protects.
Behavioral claims MUST be backed by executed code, not reasoning. Exercise the real module inline,
then discard the snippet: an ephemeral run proves behavior without leaving a test file to maintain.
Every snippet MUST print the observed value next to the expectation, so pass/fail is in the output
rather than in your interpretation of a dump.
uv run --project . python - <<'EOF'
from package_name._helpers import parse_target
r = parse_target("owner/repo#12")
print("repo:", r.repo, "| number:", r.number, "| expect owner/repo, 12")
EOF
Exercise the narrowest unit that proves the claim: a pure function over a literal input beats a full
command run. For command-level behavior, invoke the CLI the way a user does:
uv run --project . -m package_name command arg
Output that contradicts the expectation MUST be diagnosed before any code change: re-derive the
expectation from the source, then suspect the harness, and only then the code. Synthetic drivers use
hand-built inputs, so anything ordering-, timing-, or environment-dependent needs a realistic
fixture; otherwise the harness reports its own artifacts as failures.
Scratch files MUST be deleted before reporting; when a file is unavoidable, name it *.local.*.
Compliance Checklist
Every script project MUST pass all items below at all times. Verify after creating, editing,
refactoring, or reviewing any project. Fix violations in place.
Structure
Directory is kebab-case; package is snake_case equivalent
pyproject.toml present with hatchling build backend
uv.lock present and committed
Package contains __init__.py with __version__
Package contains __main__.py with exact entry pattern
Package contains _click.py with HelpfulGroup (verbatim)
Package contains _errors.py with die() and domain exception
Package contains cli.py with _AutoGroup root group
No tests/ directory and no test files anywhere in the project
No [project.scripts] in pyproject.toml; wrapper script exists instead
Dependencies
requires-python = ">=3.13"
Click is listed in dependencies (no argparse usage anywhere)
All pins use >=X.Y format
Dev dependencies in [dependency-groups] dev, not [project.optional-dependencies]
No formatting/UI libraries (rich, tabulate, colorama, tqdm, etc.)
No test framework dependency (pytest, unittest plugins) and no [tool.pytest.ini_options]
Code
Every .py file starts with from __future__ import annotations
All function signatures have type hints
die() typed as NoReturn
All output uses click.echo(), never print() (except inside die())
Errors go to stderr via click.echo(..., err=True)
No JSON/YAML/table output unless a downstream tool requires it
No ANSI colors, emoji, or decorative formatting in output
Private modules prefixed with _
No if __name__ == "__main__" except in __main__.py
External tool dependencies checked via check_deps() at startup
Subprocess calls wrapped in typed helper functions in private modules
Verification
Behavioral claims proven by an executed snippet, not reasoning