Skip to content

Commands, groups and aliases

Everything under commands becomes a command of your CLI. The key decides where the command lives and what it is called; the value decides what it does.

The snippets on this page come from one manifest, shown in full at the end.

Three ways to write a command

  # 1. A string: the whole value is the run script
  ping: print("pong")

  # 2. A list: each item is a line of the run script
  status:
    - print("checking...")
    - $ echo "all good"

  # 3. A mapping: the full form
  greet:
    help: Greet a customer
    params:
      - name: str = "friend"
    run: |
      print(f"Welcome, {name}!")

Use the short forms for one-liners and the mapping when you need help, params or anything else. The keys of the mapping are listed in the manifest reference.

$ cli run commands.yaml ping
pong
$ cli run commands.yaml greet
Welcome, friend!

Command names may contain letters, digits, - and _.

Groups

A dot in the key puts the command in a group. Groups are created as needed and can be nested.

  # A dot nests a command inside a group: `shop orders list`
  orders.create:
    help: Create an order
    params:
      - item: str!
    run: print(f"created order for {item}")

  orders.ship:
    help: Ship an order
    params:
      - number: int!
    run: print(f"shipped order {number}")

  # Groups nest as deep as you like: `shop stock warehouse count`
  stock.warehouse.count: print("42 items")

  # A command with help and no run script sets the help text of the group
  orders:
    help: Manage customer orders
$ cli run commands.yaml orders create book
created order for book
$ cli run commands.yaml stock warehouse count
42 items
$ cli run commands.yaml -- orders --help
...
╭─ Commands ─────────────────────────────────────────────────────────────────╮
│ create  Create an order                                                    │
│ ship    Ship an order                                                      │
│ list    List all orders                                                    │
╰────────────────────────────────────────────────────────────────────────────╯

To describe a group, add an entry with the group's name that has help and no run, like orders above. A name is either a group or a command: if orders had a run script while orders.create exists, the group would take the name and the script could not be reached.

Aliases

  # `shop customers add` and `shop customers new` run the same code
  customers.add|new:
    help: Add a customer
    params:
      - name: str!
    run: print(f"added {name}")

  # The aliases field does the same without touching the key
  version-info:
    help: Show build details
    aliases: [vi]
    run: print("build 7")
  • | in the key adds aliases to the last part of the name: customers.add|new gives customers add and customers new.
  • The aliases list does the same from inside the command.

Aliases are hidden from --help. When a manifest defines aliases in its keys, the CLI gets an --aliases option that lists them:

$ cli run commands.yaml -- --aliases

Command             Aliases
--------            --------
customers.add       new

One command for every group

A key that starts with (*) is added to every group. Inside that command, {(*)} is replaced with the group's name, in help, run, pre_run, post_run and params.

  # (*) adds this command to every group; {(*)} is replaced by the group name
  (*).list:
    help: List all {(*)}
    params:
      - --limit|-l: int = 10
    run: print(f"listing {limit} {(*)}")
$ cli run commands.yaml orders list
listing 10 orders
$ cli run commands.yaml -- customers list -l 3
listing 3 customers

Run scripts

run is Python. Write it as a block (run: |) or as a list of lines. Lines that start with $ are shell commands, described in Shell commands.

Two hooks wrap the script. pre_run runs before run and post_run after it, in the same function, so all three share variables.

  deploy:
    help: Deploy the shop
    pre_run: print("1. pre_run")
    run: print("2. run")
    post_run: print("3. post_run")
$ cli run commands.yaml deploy
1. pre_run
2. run
3. post_run

Hooks are most useful together with command templates, which let several commands share them.

To stop a command with an exit code, raise typer.Exit:

run: |
  if not ready:
      print("not ready")
      raise typer.Exit(code=1)

Help text

help on a command is shown in the command list and at the top of the command's own --help. Because run is the body of a Python function, a docstring on its first line works as well:

  about:
    run: |
      """Show what this CLI is for"""
      print("A demo shop")

Command settings

config passes extra settings to Typer for one command.

  migrate:
    help: Run the old migration tool
    run: print("migrating")
    config:
      deprecated: true
      hidden: true
      epilog: Use `shop deploy` instead.
Key Effect
hidden Leave the command out of --help.
deprecated Mark the command as deprecated and print a warning when it runs.
epilog Text shown at the end of the command's help.
short_help Text for the command list, when it should differ from help.
no_args_is_help Show help when the command is called without arguments.
context_settings Click context settings, for example help_option_names.

All keys are in the manifest reference.

The complete example

commands.yaml
name: shop
version: 0.1.0
help: Groups, aliases and command options

cli_options:
  add_completion: false
  no_args_is_help: true

commands:
  # 1. A string: the whole value is the run script
  ping: print("pong")

  # 2. A list: each item is a line of the run script
  status:
    - print("checking...")
    - $ echo "all good"

  # 3. A mapping: the full form
  greet:
    help: Greet a customer
    params:
      - name: str = "friend"
    run: |
      print(f"Welcome, {name}!")

  # A dot nests a command inside a group: `shop orders list`
  orders.create:
    help: Create an order
    params:
      - item: str!
    run: print(f"created order for {item}")

  orders.ship:
    help: Ship an order
    params:
      - number: int!
    run: print(f"shipped order {number}")

  # Groups nest as deep as you like: `shop stock warehouse count`
  stock.warehouse.count: print("42 items")

  # A command with help and no run script sets the help text of the group
  orders:
    help: Manage customer orders

  # `shop customers add` and `shop customers new` run the same code
  customers.add|new:
    help: Add a customer
    params:
      - name: str!
    run: print(f"added {name}")

  # The aliases field does the same without touching the key
  version-info:
    help: Show build details
    aliases: [vi]
    run: print("build 7")

  # (*) adds this command to every group; {(*)} is replaced by the group name
  (*).list:
    help: List all {(*)}
    params:
      - --limit|-l: int = 10
    run: print(f"listing {limit} {(*)}")

  deploy:
    help: Deploy the shop
    pre_run: print("1. pre_run")
    run: print("2. run")
    post_run: print("3. post_run")

  migrate:
    help: Run the old migration tool
    run: print("migrating")
    config:
      deprecated: true
      hidden: true
      epilog: Use `shop deploy` instead.

  about:
    run: |
      """Show what this CLI is for"""
      print("A demo shop")

tests:
  - ping: assert result.output == "pong\n"
  - greet: assert result.output == "Welcome, friend!\n"
  - orders create book: assert result.output == "created order for book\n"
  - orders ship 7: assert result.output == "shipped order 7\n"
  - stock warehouse count: assert result.output == "42 items\n"
  - orders --help: assert "Manage customer orders" in result.output
  - customers new Ada: assert result.output == "added Ada\n"
  - vi: assert result.output == "build 7\n"
  - orders list: assert result.output == "listing 10 orders\n"
  - customers list -l 3: assert result.output == "listing 3 customers\n"
  - orders list --help: assert "List all orders" in result.output
  - deploy: assert result.output == "1. pre_run\n2. run\n3. post_run\n"
  - migrate: assert "migrating" in result.output
  - --help: assert "migrate" not in result.output
  - about --help: assert "Show what this CLI is for" in result.output
  - --aliases: assert "customers.add" in result.output