# Commands

There are two different ways of registering functions:

1. `app.default` - Registers an action for when no registered command is provided. This was previously demonstrated in Getting Started.

   A sub-app cannot be registered with `app.default`. If no `default` command is registered, Cyclopts will display the help-page.

2. `app.command` - Registers a function or `App` as a command.

This section will detail how to use the `@app.command` decorator.

## Registering a Command

The `@app.command` decorator adds a command to a Cyclopts application.

    from cyclopts import App

    app = App()

    @app.command
    def fizz(n: int):
        print(f"FIZZ: {n}")

    @app.command
    def buzz(n: int):
        print(f"BUZZ: {n}")

    app()

We can now control which command runs from the CLI:

    $ my-script fizz 3
    FIZZ: 3

    $ my-script buzz 4
    BUZZ: 4

    $ my-script fuzz
    ╭─ Error ────────────────────────────────────────────────────────────────────╮
    │ Unknown command "fuzz". Did you mean "fizz"?                               │
    ╰────────────────────────────────────────────────────────────────────────────╯

## Registering a SubCommand

The `app.command` method can also register another Cyclopts `App` as a command.

    from cyclopts import App

    app = App()
    sub_app = App(name="foo")  # "foo" would be a better variable name than "sub_app".
    # "sub_app" in this example emphasizes the name comes from name="foo".
    app.command(sub_app)  # Registers sub_app to command "foo"
    # Or, as a one-liner:  sub_app = app.command(App(name="foo"))

    @sub_app.command
    def bar(n: int):
        print(f"BAR: {n}")

    # Alternatively, access subapps from app like a dictionary.
    @app["foo"].command
    def baz(n: int):
        print(f"BAZ: {n}")

    app()

    $ my-script foo bar 3
    BAR: 3

    $ my-script foo baz 4
    BAZ: 4

The subcommand may have their own registered `default` action. Cyclopts's command structure is fully recursive.

## Flattening SubCommands

Sometimes you want to make all commands from a sub-app directly accessible from the parent app, without requiring users to type the intermediate subcommand name.

You can flatten a sub-app by registering it with the special `name="*"`:

    from cyclopts import App

    app = App()
    tools_app = App(name="tools")

    @tools_app.command
    def compress(file: str):
        print(f"Compressing {file}")

    @tools_app.command
    def extract(file: str):
        print(f"Extracting {file}")

    # Flatten: make all tools_app commands directly accessible
    app.command(tools_app, name="*")

    app()

    $ my-script compress data.txt
    Compressing data.txt

    $ my-script extract archive.zip
    Extracting archive.zip

Caveats of flattening:

- Parent app commands take precedence over flattened commands if there are name collisions.

- Multiple sub-apps can be flattened into the same parent app.

- You cannot supply additional configuration kwargs when using `name="*"`.

- Only `App` instances can be flattened (not functions or import paths).

Flattening is useful for organizing related commands into logical groups in your code while keeping the CLI interface simple and flat.

## SubCommand Configuration

Subcommands inherit configuration from their parent apps.

    from cyclopts import App

    # Root app with specific error handling
    root_app = App(
        exit_on_error=False,
        print_error=False,
    )

    # Child app inherits parent's settings
    child_app = root_app.command(App(name="child"))

    @child_app.default
    def child_action():
        return "Child executed successfully"

    # Child can override parent settings if needed
    grandchild_app = child_app.command(App(name="grandchild", exit_on_error=True))

When `parent_app("child ...")` is called, the child command will use the parent's error handling settings unless explicitly overridden.

## Changing Command Name

By default, commands are registered to the python function's name with underscores replaced with hyphens. Any leading or trailing underscores will be stripped. For example, the function `_foo_bar()` will become the command `foo-bar`. This renaming is done because CLI programs generally tend to use hyphens instead of underscores. The name transform can be configured by `App.name_transform`. For example, to make CLI command names be identical to their python function name counterparts, we can configure `App` as follows:

    from cyclopts import App

    app = App(name_transform=lambda s: s)

    @app.command
    def foo_bar():  # will now be "foo_bar" instead of "foo-bar"
        print("running function foo_bar")

    app()

    $ my-script foo_bar
    running function foo_bar

Alternatively, the name can be manually changed in the `@app.command` decorator. Manually set names are not subject to `App.name_transform`.

    from cyclopts import App

    app = App()

    @app.command(name="bar")
    def foo():  # function name will NOT be used.
        print("Hello World!")

    app()

    $ my-script bar
    Hello World!

Finally, if you would like to register an additional name to the Cyclopts-derived names, you can set an `alias`:

    from cyclopts import App

    app = App()

    @app.command(alias="bar")
    def foo():  # both "foo" and "bar" will trigger this function.
        print("Running foo.")

    app()

    $ my-script foo
    Running bar.

    $ my-script bar
    Running bar.

## Synonyms

A common CLI problem is remembering subcommand synonyms. Did this CLI's developer decide to call it `remove`, `uninstall`, or `delete`?

The `synonym` field declares alternate names that trigger a "Did you mean..." suggestion without registering the command under those names. It accepts either a single `str` or an iterable of strings:

    from cyclopts import App

    app = App()

    @app.command(synonym="add")               # single string
    def install(name: str): ...

    @app.command(synonym=["remove", "rm"])    # list of strings
    def uninstall(name: str):
        print(f"uninstalling {name}")

    app()

    $ my-script uninstall mypackage
    uninstalling mypackage

    $ my-script remove mypackage
    ╭─ Error ────────────────────────────────────────────────────────────────────╮
    │ Unknown command "remove". Did you mean "uninstall"? Available commands:    │
    │ uninstall.                                                                 │
    ╰────────────────────────────────────────────────────────────────────────────╯

Synonyms differ from `alias` in two important ways:

- Synonyms are not runnable. `my-script remove` raises an error; it doesn't invoke `uninstall`. Use `alias` when you want the command to be callable under multiple names.

- Synonyms are hidden from `--help`. They exist only to guide users who type the "wrong" word.

If two commands declare the same synonym, both are suggested:

    @app.command(synonym="erase")
    def uninstall(name: str): ...

    @app.command(synonym="erase")
    def purge(name: str): ...

    $ my-script erase mypackage
    ╭─ Error ────────────────────────────────────────────────────────────────────╮
    │ Unknown command "erase". Did you mean "uninstall" or "purge"? ...          │
    ╰────────────────────────────────────────────────────────────────────────────╯

When a typed token is not declared as any synonym, Cyclopts falls back to its standard typo-fuzzy-match suggestion.

## Adding Help

There are a few ways to add a help string to a command:

1. If the function has a docstring, the short description will be used as the help string for the command. This is generally the preferred method of providing help strings.

2. If the registered command is a sub app, the sub app's `help` field will be used.

       sub_app = App(name="foo", help="Help text for foo.")
       app.command(sub_app)

3. The `help` field of `@app.command`. If provided, the docstring or subapp help field will not be used.

       from cyclopts import App

       app = App()

       @app.command
       def foo():
           """Help string for foo."""
           pass

       @app.command(help="Help string for bar.")
       def bar():
           """This got overridden."""

       app()

       $ my-script --help
       ╭─ Commands ────────────────────────────────────────────────────────────╮
       │ bar        Help string for bar.                                       │
       │ foo        Help string for foo.                                       │
       │ --help,-h  Display this message and exit.                             │
       │ --version  Display application version.                               │
       ╰───────────────────────────────────────────────────────────────────────╯

## Async

Cyclopts also works with async commands; when an async command is encountered, an event loop will be automatically created using the specified `backend` parameter (default `asyncio`).

    import asyncio
    from cyclopts import App

    app = App()

    @app.command
    async def foo():
        await asyncio.sleep(10)

    app()

When calling from within an existing async context, `await` the async method `run_async()`:

    async def main():
        result = await app.run_async(["foo"])
        # Instead of: app(["foo"]) which would raise RuntimeError

## Decorated Function Details

Cyclopts does not modify the decorated function in any way. The returned function is the exact same function being decorated and can be used exactly as if it were not decorated by Cyclopts.

## See Also

For improved CLI startup performance with large applications, see Lazy Loading.