Quickstart¶
Five minutes from pip install to your first clickwork command.
What you'll build¶
A minimal CLI named greet with one command, greet hello, that
takes a --name flag.
Prerequisites¶
- Python 3.11 or newer
- A fresh directory to work in
Step 1 — install¶
Verify:
You should see 1.0.0 (or whatever the latest is).
Step 2 — create the project layout¶
Work from a parent directory containing a greet/ subdirectory:
mkdir -p greet/commands
# Stay in the PARENT directory — don't cd into greet/. We'll keep
# cwd fixed so the file paths below (greet/cli.py, greet/commands/
# hello.py) and the run command in step 3 (python greet/cli.py ...)
# all agree about where things live.
Create the entry point greet/cli.py:
from pathlib import Path
from clickwork import create_cli
cli = create_cli(
name="greet",
commands_dir=Path(__file__).parent / "commands",
)
if __name__ == "__main__":
cli()
commands_dir is typed as pathlib.Path, not str — clickwork's
discovery calls .is_dir() and .glob() on it directly. Using
Path(__file__).parent / "commands" makes the path resolve relative
to the cli.py file, so the CLI works from any working directory.
And your first command greet/commands/hello.py:
import click
@click.command(name="hello")
@click.option("--name", default="world", help="Who to greet.")
def cli(name: str) -> None:
"""Say hello."""
click.echo(f"Hello, {name}!")
Why name="hello": clickwork keys each registered command off the
Click command's .name attribute (falling back to the filename only
if .name is unset). When you write @click.command() on a function
called cli, Click derives the name as "cli" — which means without
the explicit name="hello", this file would register as a command
named cli (and would collide with any other file that did the
same). Setting name= explicitly is the safest pattern.
Step 3 — run it¶
From the parent directory of greet/ (the dir you've been working
in — don't cd greet/):
Expected:
You've just written a clickwork CLI.
What just happened¶
create_cli()returned a ClickGroupconfigured to load commands fromgreet/commands/.- Each file in that directory that exposes a
cliattribute becomes a subcommand, using the Click command's own.name(falling back to the filename stem only when.nameis unset). That's why the example uses@click.command(name="hello")— without the explicit name, Click would derive it from the decorated function's name (cli), and every such file would collide on the namecli. - The
--nameoption is plain Click — clickwork doesn't get in Click's way.
Where to next¶
- Practical Walkthrough — build a realistic multi-command CLI with a plugin.
- User Guide — the full reference.
- How-To: Tame a script directory — if you arrived with an existing pile of scripts rather than a blank slate.