cli-module

📁 psincraian/myfy 📅 1 day ago
0
总安装量
1
周安装量
安装命令
npx skills add https://github.com/psincraian/myfy --skill cli-module

Agent 安装分布

amp 1
cline 1
opencode 1
cursor 1
continue 1
kimi-cli 1

Skill 文档

CliModule – Custom CLI Commands

CliModule enables custom CLI commands with full dependency injection and Typer integration.

Quick Start

from myfy.core import Application
from myfy.commands import CliModule, cli

@cli.command()
async def seed_users(user_service: UserService, count: int = 10):
    '''Seed the database with test users.'''
    for i in range(count):
        await user_service.create(f"user{i}@example.com")
    print(f"Created {count} users")

app = Application()
app.add_module(CliModule())

# Run with: myfy app seed-users --count 20

Configuration

Environment variables use the MYFY_CLI_ prefix:

Variable Default Description
MYFY_CLI_VERBOSE False Enable verbose output
MYFY_CLI_NO_COLOR False Disable colored output
MYFY_CLI_TIMEOUT 300 Command timeout (seconds)

Defining Commands

Basic Command

from myfy.commands import cli

@cli.command()
async def hello():
    '''Say hello.'''
    print("Hello, World!")

# Run: myfy app hello

With DI Injection

Services are automatically injected:

from myfy.commands import cli
from myfy.data import AsyncSession

@cli.command()
async def count_users(session: AsyncSession):
    '''Count users in the database.'''
    result = await session.execute(select(func.count(User.id)))
    count = result.scalar()
    print(f"Total users: {count}")

# Run: myfy app count-users

With Arguments

@cli.command()
async def greet(name: str):
    '''Greet someone by name.'''
    print(f"Hello, {name}!")

# Run: myfy app greet John

With Options

@cli.command()
async def seed(count: int = 10, verbose: bool = False):
    '''Seed the database.'''
    for i in range(count):
        if verbose:
            print(f"Creating user {i + 1}/{count}")
        await create_user(i)
    print(f"Created {count} users")

# Run: myfy app seed --count 50 --verbose

Command Groups

Organize related commands:

from myfy.commands import cli

# Create a group
db = cli.group("db")

@db.command()
async def seed(session: AsyncSession):
    '''Seed the database.'''
    await seed_data(session)

@db.command()
async def reset(session: AsyncSession, force: bool = False):
    '''Reset the database.'''
    if force or confirm("Are you sure?"):
        await reset_data(session)

# Run: myfy app db:seed
#      myfy app db:reset --force

Typer Integration

Use Typer for advanced CLI features:

import typer
from myfy.commands import cli

@cli.command()
async def import_data(
    db: Database,  # DI injection
    file: str = typer.Argument(..., help="Path to import file"),
    dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Simulate import"),
    format: str = typer.Option("json", "--format", "-f", help="File format"),
):
    '''Import data from a file.'''
    if dry_run:
        print(f"Would import from {file} ({format} format)")
    else:
        await db.import_from_file(file, format)

# Run: myfy app import-data users.csv --format csv --dry-run

Typer Features

import typer

@cli.command()
async def deploy(
    # Required argument
    env: str = typer.Argument(..., help="Target environment"),
    # Optional with short form
    version: str = typer.Option(None, "--version", "-v"),
    # Boolean flag
    skip_tests: bool = typer.Option(False, "--skip-tests"),
    # Choice from enum
    region: Region = typer.Option(Region.US, "--region", "-r"),
):
    '''Deploy the application.'''
    ...

# Run: myfy app deploy production -v 1.2.3 --skip-tests --region eu

Parameter Classification

Parameters are automatically classified:

Pattern Classification
typer.Argument(...) CLI positional argument
typer.Option(...) CLI option/flag
Primitive with default CLI option
Primitive without default CLI argument
Complex type DI dependency
@cli.command()
async def process(
    file: str,                    # CLI argument (required)
    count: int = 10,              # CLI option (--count)
    verbose: bool = False,        # CLI flag (--verbose)
    session: AsyncSession,        # DI injection
    settings: AppSettings,        # DI injection
):
    ...

Custom Command Names

@cli.command(name="import-users")  # Explicit name
async def import_users_handler(file: str):
    '''Import users from file.'''
    ...

# Run: myfy app import-users users.csv

Function name import_users_handler becomes command name import-users by default (underscores to hyphens).

Async vs Sync

Both async and sync handlers are supported:

# Async (recommended)
@cli.command()
async def async_task(session: AsyncSession):
    await session.execute(...)

# Sync (also works)
@cli.command()
def sync_task(settings: AppSettings):
    print(settings.app_name)

Error Handling

@cli.command()
async def risky_operation(session: AsyncSession):
    '''Perform a risky operation.'''
    try:
        await do_risky_thing(session)
    except RiskyError as e:
        print(f"Error: {e}", file=sys.stderr)
        raise typer.Exit(1)

Progress and Output

import typer

@cli.command()
async def process_files(files: list[str]):
    '''Process multiple files.'''
    with typer.progressbar(files) as progress:
        for file in progress:
            await process_file(file)

    typer.echo(typer.style("Done!", fg=typer.colors.GREEN))

Running Commands

# List available commands
myfy app --help

# Run a command
myfy app seed-users

# With options
myfy app seed-users --count 50

# Grouped command
myfy app db:reset --force

# With arguments
myfy app import users.csv

Best Practices

  1. Use docstrings – Become command help text
  2. Group related commands – Easier to discover
  3. Use Typer for complex args – Better help messages
  4. Handle errors gracefully – Use typer.Exit(1) for failures
  5. Show progress – Use typer.progressbar for long operations
  6. Inject services – Don’t create DB connections manually
  7. Keep commands focused – One task per command