# How to Add Custom Command Groups to an Existing CLI-Anything Harness

> Easily add custom command groups to your CLI Anything harness. Define groups with @cli.group(), attach sub-commands, and sync docs automatically. Get started now.

- Repository: [✨Data Intelligence Lab@HKU✨/CLI-Anything](https://github.com/HKUDS/CLI-Anything)
- Tags: how-to-guide
- Published: 2026-05-18

---

**To add custom command groups to a CLI-Anything harness, define a function decorated with `@cli.group()` in your `<software>_cli.py` module, attach sub-commands using `@<group>.command()`, and run the skill generator to sync the documentation automatically.**

CLI-Anything enables rapid construction of AI-agent skills by wrapping software functionality into structured command-line interfaces. When you need to **add custom command groups to an existing CLI harness**, the framework's source-driven discovery system automatically parses your Python code to update the skill metadata without requiring manual configuration changes.

## Understanding the CLI-Anything Discovery Mechanism

The harness discovers command hierarchies by statically analyzing your CLI source code. In [`zotero/agent-harness/skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/zotero/agent-harness/skill_generator.py), the `extract_commands_from_cli` function uses Python's `ast` module to parse the `<software>_cli.py` file and build a metadata model.

The discovery process follows four distinct phases:

1. **AST Parsing** – The file is read and parsed into an abstract syntax tree using `ast.parse`.

2. **Group Identification** – The helper `_click_decorator_info` scans for functions decorated with `@cli.group`. When the decorator owner is `"cli"` and the decorator name is `"group"`, the function is registered as a top-level command group. This logic appears in lines 21–25 of [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py).

3. **Name Resolution** – The display name is determined by `_default_group_name`, which strips trailing `_group` suffixes, replaces underscores with spaces, and applies title casing. If the decorator includes an explicit `name` argument, that value takes precedence.

4. **Command Collection** – A second pass identifies functions decorated with `@<group>.command`. The parser resolves the owning group via the `group_name_by_function` mapping and extracts command names using `_default_command_name`, while docstrings are captured via `ast.get_docstring` for descriptions.

Because this metadata extraction is entirely source-driven, **adding a new command group requires only modifying the CLI source file**—no generator or pipeline updates are necessary.

## Step-by-Step Guide to Adding Custom Command Groups

### Create the Group Function

Open your `<software>_cli.py` file and define a new function decorated with `@cli.group()`. Provide an explicit name if the automatic transformation doesn't meet your needs.

```python
@cli.group()
def report() -> None:
    """Generate high-level reports about the current Library."""
    pass

```

The docstring becomes the group description in the generated [`SKILL.md`](https://github.com/HKUDS/CLI-Anything/blob/main/SKILL.md), as retrieved by `ast.get_docstring(node)` in [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py) (lines 29–31).

### Add Sub-Commands to the Group

Define commands under the new group using the `@<group>.command("<sub-cmd>")` decorator pattern. Each command should include a descriptive docstring.

```python
@report.command("library-summary")
@click.argument("library", required=False)
@click.pass_context
def library_summary(ctx: click.Context, library: str | None) -> int:
    """
    Show a brief summary of a user or group library.
    
    The summary includes the number of collections, items, and tags.
    """
    # Implementation logic here

    return 0

```

The generator's second pass detects these decorators by checking if the owner name matches a previously recorded group in `group_by_display_name`, then appends the command to that group's command list (lines 138–145).

### Generate Updated Documentation

Run the skill-generation script to verify that the new group appears in the documentation:

```bash
python -m cli_anything.zotero generate-skill

```

This command re-parses [`zotero_cli.py`](https://github.com/HKUDS/CLI-Anything/blob/main/zotero_cli.py) using the same `extract_commands_from_cli` logic, ensuring your new `report` group and its `library-summary` command appear in the output [`SKILL.md`](https://github.com/HKUDS/CLI-Anything/blob/main/SKILL.md) without additional configuration.

## Complete Example: Adding a Report Group to Zotero

Here is a complete implementation extending the Zotero harness with a `report` command group:

```python

# File: zotero/agent-harness/cli_anything/zotero/zotero_cli.py

import click
from . import catalog, discovery, session_mod

# ... existing code ...

@cli.group()
def report() -> None:
    """Generate high-level reports about the current Library."""
    pass

@report.command("library-summary")
@click.argument("library", required=False)
@click.pass_context
def library_summary(ctx: click.Context, library: str | None) -> int:
    """
    Show a brief summary of a user or group library.

    The summary includes the number of collections, items, and tags.
    """
    runtime = discovery.current_runtime(ctx)
    lib_id = catalog.resolve_library_id(runtime, library) if library else None
    summary = catalog.library_summary(runtime, lib_id)
    # emit function assumed available in scope

    emit(ctx, summary)
    return 0

```

After adding this code and running `python -m cli_anything.zotero generate-skill`, the generated [`SKILL.md`](https://github.com/HKUDS/CLI-Anything/blob/main/SKILL.md) will contain:

```markdown

### Report

Generate high-level reports about the current Library.

| Command | Description |
|---------|-------------|
| library-summary | Show a brief summary of a user or group library. |

```

## How the Generator Recognizes New Groups

The [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py) utility relies on specific AST inspection patterns to identify groups dynamically:

- **Group Detection**: The condition `owner_name == "cli"` and `decorator_name == "group"` in `extract_commands_from_cli` identifies your new function as a command group root (source lines 21–25).

- **Decorator Analysis**: The `_click_decorator_info` helper (lines 86–102) dissects decorator chains to resolve the owning object name and the decorator type, distinguishing between `@cli.group` and `@cli.command`.

- **Mapping Resolution**: The parser maintains a `group_name_by_function` dictionary that maps function names to their display names. When processing `@report.command`, the generator resolves `owner_name` to `"report"`, finds the matching group entry, and appends the command using `group_by_display_name[display_name].commands.append(...)`.

This architecture ensures that any new group defined with the standard Click patterns is automatically incorporated into the skill hierarchy.

## Summary

- **Source-driven metadata**: CLI-Anything uses AST parsing in [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py) to discover groups decorated with `@cli.group()` and commands under `@<group>.command()` without requiring configuration file updates.
- **Simple addition process**: Define a function with `@cli.group()`, add a docstring for the description, implement sub-commands with the group-specific decorator, and run `python -m cli_anything.<software> generate-skill`.
- **Automatic documentation**: The generator creates human-readable [`SKILL.md`](https://github.com/HKUDS/CLI-Anything/blob/main/SKILL.md) files reflecting the current CLI structure, keeping documentation synchronized with code changes.
- **No generator modifications needed**: Because `extract_commands_from_cli` handles discovery generically, you only edit your `<software>_cli.py` file to extend functionality.

## Frequently Asked Questions

### Do I need to modify skill_generator.py to add new command groups?

No. The [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py) file is designed to parse any valid Click CLI structure automatically. You only need to add your `@cli.group()` decorated function to your `<software>_cli.py` module. The generator's AST parser will detect the new group during the next run of `generate-skill`.

### How does CLI-Anything determine the display name for a command group?

The framework uses the `_default_group_name` helper function in [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py). It removes trailing `_group` suffixes, replaces underscores with spaces, and applies title casing to the function name. Alternatively, you can pass an explicit name to the decorator, such as `@cli.group("custom-name")`, which overrides the automatic transformation.

### Can I nest command groups within other groups?

The current implementation in [`skill_generator.py`](https://github.com/HKUDS/CLI-Anything/blob/main/skill_generator.py) primarily discovers top-level groups owned by the main `cli` object and their immediate sub-commands. While Click supports multi-level group nesting, the standard CLI-Anything harness pattern focuses on flat group structures under the main CLI. For deeply nested hierarchies, you would need to verify that the `_click_decorator_info` resolver correctly tracks the ownership chain through the `group_name_by_function` mapping.

### Where is the generated documentation stored after running the skill generator?

The `generate-skill` command outputs a [`SKILL.md`](https://github.com/HKUDS/CLI-Anything/blob/main/SKILL.md) file within the per-skill folder structure, typically located at `zotero/agent-harness/cli_anything/zotero/skills/` or the equivalent path for your specific software module. This markdown file contains the structured documentation of all discovered command groups and their associated commands.