dev-cli-development

ai-debugger-inc's avatarfrom ai-debugger-inc

Guide for developing AIDB dev-cli commands and services. Covers Click framework patterns, service architecture, command development, decorators, error handling, CommandExecutor, CliOutput, BaseService, BaseManager patterns. Use when working with src/aidb_cli/, adding CLI commands, creating services, or integrating with Docker/test/adapter systems.

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 2, 2026

When & Why to Use This Skill

This Claude skill serves as a comprehensive development framework and guide for building AIDB dev-cli commands and services. It provides standardized architectural patterns, including the Click framework integration, BaseService patterns for business logic, and unified error handling. By offering clear templates for command registration, service instantiation, and output management, it ensures high-quality, maintainable, and consistent command-line tool development within the AIDB ecosystem.

Use Cases

  • Developing new CLI command groups and subcommands using the Click framework with optimized decorator stacking and context management.
  • Implementing reusable business logic services by extending the BaseService pattern to ensure consistent dependency injection and command execution.
  • Standardizing error handling and user-facing output across developer utilities using the @handle_exceptions decorator and CliOutput strategy.
  • Integrating command-line tools with complex backend systems such as Docker orchestration, test coordinators, and adapter build systems.
  • Creating custom parameter types for CLI inputs to provide robust validation and shell autocompletion for specialized developer workflows.
namedev-cli-development
descriptionGuide for developing AIDB dev-cli commands and services. Covers Click

Dev-CLI Development Guide

Purpose

The AIDB dev-cli (src/aidb_cli/) is a Click-based command-line interface for AIDB development workflows. This skill guides developers in:

  • Adding new CLI commands and command groups
  • Creating reusable services following BaseService pattern
  • Using custom decorators (@handle_exceptions, @require_repo_context)
  • Integrating with Docker, test, and adapter systems
  • Following Click framework best practices
  • Proper error handling and output formatting

When to Use This Skill

Auto-activates when:

  • Editing files in src/aidb_cli/
  • Mentioning "dev-cli", "CLI command", "Click", "CliOutput"
  • Adding commands, services, or CLI utilities
  • Working with test orchestration or Docker integration

When developing CLI commands, you may also need:

  • testing-strategy - CLI orchestrates test execution via test coordinator service
  • code-reuse-enforcement - CLI must use existing constants and avoid magic strings

Architecture Overview

For comprehensive architecture details, see docs/developer-guide/cli-reference.md and source code in src/aidb_cli/.

Component Structure

src/aidb_cli/
├── commands/      # CLI command definitions (13 modules)
├── services/      # Business logic services (41+ files)
├── managers/      # High-level orchestration (singleton pattern)
├── core/          # Decorators, utilities, constants, param types
└── generators/    # Code generation for test scenarios

Key Patterns

  1. Commands - Thin wrappers that delegate to services
  2. Services - Reusable business logic with CommandExecutor
  3. Managers - Singleton orchestrators for complex workflows
  4. Context Injection - Dependencies via Click's ctx.obj
  5. Unified Error Handling - @handle_exceptions decorator
  6. Dynamic Parameter Types - Custom types with shell completion

Quick Start: Adding a Command

Step 1: Create Command File

Create src/aidb_cli/commands/mycommand.py:

"""My new command description."""

import click

from aidb_cli.core.decorators import handle_exceptions
from aidb_logging import get_cli_logger

logger = get_cli_logger(__name__)


@click.group(name="mycommand")
@click.pass_context
def group(ctx: click.Context) -> None:
    """Command group description."""
    pass


@group.command()
@click.option("--option", "-o", help="Option description")
@click.pass_context
@handle_exceptions
def subcommand(ctx: click.Context, option: str) -> None:
    """Subcommand description."""
    # Get dependencies from context
    output = ctx.obj.output
    repo_root = ctx.obj.repo_root
    executor = ctx.obj.command_executor

    # Delegate to service
    from aidb_cli.services.myservice import MyService
    service = MyService(repo_root, executor)
    result = service.do_something(option)

    # User-facing output (via OutputStrategy)
    output.success(f"Done: {result}")

Step 2: Register Command

In src/aidb_cli/cli.py:

from aidb_cli.commands import mycommand

# In main CLI setup
cli.add_command(mycommand.group)

Step 3: Test

./dev-cli mycommand subcommand --option value

Command Development Patterns

Decorator Order (CRITICAL)

Always stack decorators in this exact order:

@group.command()                    # 1. Click command
@click.option("--opt", "-o")        # 2. Options
@click.pass_context                 # 3. Context (BEFORE handle_exceptions)
@handle_exceptions                  # 4. Error handling (LAST)
def command(ctx: click.Context, opt: str) -> None:
    """Implementation."""

Wrong order causes cryptic errors!

Custom Parameter Types

Use custom types for validation and autocompletion:

from aidb_cli.core.param_types import TestSuiteParamType

@click.option("--suite", type=TestSuiteParamType(), required=True)
def command(ctx: click.Context, suite: str) -> None:
    """Command with validated suite parameter."""

Available: TestSuiteParamType, LanguageParamType, DockerProfileParamType, TestMarkerParamType

For comprehensive Click patterns, see click-framework-patterns.md:

  • Decorator stacking rules explained
  • All custom parameter types with examples
  • Context management patterns
  • Error handling with @handle_exceptions
  • Command groups and subcommands
  • Advanced Click patterns

Service Development Patterns

Most services extend BaseService for consistent dependency injection and utilities:

from aidb_cli.managers.base.service import BaseService

class MyService(BaseService):
    def __init__(self, repo_root: Path, command_executor=None, ctx=None):
        super().__init__(repo_root, command_executor, ctx)
        # Inherited: command_executor, resolved_env, logging methods, path utilities

    def do_something(self) -> str:
        self.log_info("Starting operation...")
        return self.command_executor.execute(["cmd"], cwd=self.repo_root).stdout

See service-patterns.md for comprehensive BaseService details, advanced patterns, and testing examples.

Standalone Service Pattern

For simple services without BaseService:

class MyService:
    """Service for my operations."""

    def __init__(
        self,
        repo_root: Path,
        command_executor: CommandExecutor,
        logger: Logger | None = None
    ):
        self.repo_root = repo_root
        self.executor = command_executor
        self.logger = logger or get_cli_logger(__name__)

    def do_something(self, arg: str) -> str:
        """Do something useful."""
        from aidb.common.errors import AidbError

        result = self.executor.execute(["command", arg], cwd=self.repo_root)
        if result.returncode != 0:
            raise AidbError(f"Operation failed: {result.stderr}")
        return result.stdout.strip()

Key principles:

  • Accept dependencies in __init__ (testable)
  • Use CommandExecutor for all subprocess calls
  • Log with get_cli_logger(__name__)
  • Raise exceptions for errors

For comprehensive service patterns, see service-patterns.md:

  • BaseService detailed patterns
  • CommandExecutor comprehensive guide
  • Service composition and singletons
  • Testing services
  • Real examples from AIDB codebase

Error Handling

@handle_exceptions Decorator

This decorator provides unified error handling:

@handle_exceptions
def command(ctx: click.Context) -> None:
    """Command with automatic error handling."""
    # Just raise errors naturally - decorator handles:
    # - KeyboardInterrupt (cleanup Docker resources)
    # - AidbError (formatted error output)
    # - FileNotFoundError (specific exit code)
    # - PermissionError (specific exit code)
    # - Generic exceptions (traceback in verbose mode)

    if not valid:
        from aidb.common.errors import AidbError
        raise AidbError("Validation failed")

Exit Codes

Use standard exit codes:

from aidb_cli.core.constants import ExitCode

if error:
    CliOutput.error("Operation failed")
    ctx.exit(ExitCode.GENERAL_ERROR)  # 1

if not found:
    ctx.exit(ExitCode.NOT_FOUND)  # 2

if config_error:
    ctx.exit(ExitCode.CONFIG_ERROR)  # 3

Output and Logging

OutputStrategy for Commands

Commands use ctx.obj.output (OutputStrategy) for verbosity-aware user-facing output:

output = ctx.obj.output

output.success("Operation completed")  # Always visible (green)
output.error("Operation failed")       # Always visible (red, stderr)
output.warning("Potential issue")      # Always visible (yellow)
output.plain("Regular message")        # Always visible (no color)
output.section("Title", Icons.ROCKET)  # Always visible (with separator)
output.info("Verbose detail")          # Only with -v flag
output.debug("Debug trace")            # Only with -vvv flag

CliOutput for Services/Managers

Services and managers (without Click context) use the static CliOutput utility:

from aidb_cli.core.utils import CliOutput

CliOutput.success("Operation completed successfully")
CliOutput.error("Operation failed")

Logger for Debugging

Use logger for debugging/trace information:

from aidb_logging import get_cli_logger

logger = get_cli_logger(__name__)

logger.debug("Detailed debugging info")
logger.info("General info")

Rule: Commands → ctx.obj.output, Services → CliOutput, Debugging → logger

Verbosity Levels

CLI supports three verbosity levels via global flags:

Flag Log Level Enabled Features
(none) INFO Standard output only
-v DEBUG + AIDB_ADAPTER_TRACE=1
-vvv TRACE + AIDB_ADAPTER_TRACE=1 + AIDB_CONSOLE_LOGGING=1

What each level includes:

  • INFO: User-facing milestones (session started, breakpoint hit, etc.)
  • DEBUG: Operation summaries, state transitions, adapter lifecycle
  • TRACE: Full DAP/LSP JSON payloads, receiver timing, protocol details

Examples:

./dev-cli test run -s unit          # INFO level
./dev-cli -v test run -s unit       # DEBUG + adapter traces
./dev-cli -vvv test run -s unit     # TRACE + protocol payloads

Use -vvv for maximum observability when debugging DAP/LSP protocol issues.

Common Patterns

Import Organization

Follow project standard: stdlib → third-party → AIDB core → CLI → logging. All imports at top unless avoiding circular dependency.

Avoiding circular imports: Use TYPE_CHECKING for type-only imports:

from typing import TYPE_CHECKING

import click

if TYPE_CHECKING:
    from aidb_cli.cli import Context  # Only imported for type checking

See CLAUDE.md for full style details.

Service Instantiation in Commands

@group.command()
@click.pass_context
@handle_exceptions
def command(ctx: click.Context) -> None:
    """Command that uses a service."""
    # Instantiate service with dependencies from context
    from aidb_cli.services.adapter.adapter_build_service import AdapterBuildService

    service = AdapterBuildService(
        repo_root=ctx.obj.repo_root,
        command_executor=ctx.obj.command_executor
    )

    # Call service method
    result = service.build_locally(["python"], verbose=False)

    # Output result
    CliOutput.success(f"Built adapter: {result}")

Common Pitfalls

1. Decorator Order

Wrong: @handle_exceptions before @click.pass_context Right: @click.pass_context then @handle_exceptions

2. Using subprocess Directly

Wrong: subprocess.run(["cmd"]) Right: ctx.obj.command_executor.execute(["cmd"])

3. Mixing Output Types

Wrong: print("Success") or logger.info("User message") Right: CliOutput.success("Message") for users, logger.debug() for debugging

4. Hardcoded Paths

Wrong: Path("/path/to/repo") Right: ctx.obj.repo_root / "relative/path"

Real Code Examples

Example 1: Simple Command

From src/aidb_cli/commands/docker.py:

@group.command()
@click.option("--profile", "-p", type=DockerProfileParamType(), default=None)
@click.option("--no-cache", is_flag=True, help="Build without cache")
@click.pass_context
@handle_exceptions
def build(ctx: click.Context, profile: str | None, no_cache: bool) -> None:
    """Build Docker images."""
    from aidb_cli.services.docker.docker_build_service import DockerBuildService

    service = DockerBuildService(
        repo_root=ctx.obj.repo_root,
        command_executor=ctx.obj.command_executor
    )

    result = service.build_images(profile=profile, no_cache=no_cache)
    CliOutput.success(f"Build complete: {result}")

Example 2: Service with CommandExecutor

See real implementation: src/aidb_cli/services/docker/docker_build_service.py

Key pattern - services delegate to CommandExecutor and raise exceptions on failure:

class DockerBuildService:
    def __init__(self, repo_root: Path, command_executor: CommandExecutor):
        self.repo_root = repo_root
        self.executor = command_executor

    def build_images(self, profile: str | None = None) -> int:
        """Build Docker images."""
        from aidb.common.errors import AidbError

        cmd = ["docker-compose", "build"]
        if profile:
            cmd.extend(["--profile", profile])

        result = self.executor.execute(cmd, cwd=self.repo_root)
        if result.returncode != 0:
            raise AidbError(f"Build failed: {result.stderr}")
        return 0

Testing Your Changes

Run CLI Locally

./dev-cli mycommand subcommand --option value    # Run command
./dev-cli -v mycommand subcommand                # Verbose mode
./dev-cli --dry-run mycommand subcommand         # Dry run (no execution)

Unit Testing Services

Test services by mocking CommandExecutor:

from unittest.mock import Mock
from pathlib import Path

def test_my_service():
    """Test service logic without executing commands."""
    executor = Mock()
    executor.execute.return_value = Mock(returncode=0, stdout="success")

    service = MyService(Path("/tmp"), executor)
    result = service.do_something("arg")

    assert result == "success"
    executor.execute.assert_called_once_with(["command", "arg"], cwd=Path("/tmp"))

See actual test examples: src/tests/aidb_cli/commands/integration/ for command integration tests and src/tests/aidb_cli/services/ for service unit tests

Service Discovery Guide

Common tasks and which services to use:

Task Service Location
Build Docker images DockerBuildService services/docker/docker_build_service.py
Generate docker-compose.yaml ComposeGeneratorService services/docker/compose_generator_service.py
Track Docker image checksums DockerImageChecksumService services/docker/docker_image_checksum_service.py
Track framework deps checksums FrameworkDepsChecksumService services/docker/framework_deps_checksum_service.py
Check Docker health DockerHealthService services/docker/docker_health_service.py
Run tests TestCoordinatorService services/test/test_coordinator_service.py
Build adapters AdapterBuildService services/adapter/adapter_build_service.py
Execute commands CommandExecutor services/command_executor/__init__.py
Generate test programs Generator generators/core/generator.py

Full service list: See src/aidb_cli/services/ subdirectories - docker/ (Docker operations), test/ (test execution), adapter/ (adapter building), docs/ (documentation), command_executor/ (subprocess execution)

Resource Files

For deeper dives into specific topics, see:

  • service-patterns.md - Comprehensive service architecture patterns, CommandExecutor guide, testing, real examples
  • click-framework-patterns.md - Click decorator stacking, custom parameter types, context management, error handling
  • docker-compose-generation.md - Template-based docker-compose.yaml generation, Jinja2 templates, languages.yaml configuration, hash-based cache invalidation
  • checksum-services.md - ChecksumServiceBase pattern, DockerImageChecksumService, FrameworkDepsChecksumService, container lifecycle tracking, creating custom checksum services