# Plugin Development Tutorial

## Table of Contents

- [Introduction](#introduction)
- [Plugin Architecture Overview](#plugin-architecture-overview)
- [Creating Your First Plugin](#creating-your-first-plugin)
- [Plugin Lifecycle](#plugin-lifecycle)
- [Dependency Injection](#dependency-injection)
- [Registering Slash Commands](#registering-slash-commands)
- [Hook System Integration](#hook-system-integration)
- [Status Bar Integration](#status-bar-integration)
- [Configuration Integration](#configuration-integration)
- [Complete Working Example](#complete-working-example)
- [Testing Strategies](#testing-strategies)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)

## Introduction

Kollabor CLI's plugin system allows you to extend the application's functionality in powerful ways. The core philosophy is **"everything has a hook"** - every operation in the application can be intercepted, modified, or enhanced through plugins.

This tutorial will guide you through creating plugins from scratch, covering:

- Basic plugin structure and lifecycle
- Registering custom slash commands with subcommands
- Integrating with the hook system for event interception
- Adding status bar indicators
- Managing configuration
- Testing your plugins

## Plugin Architecture Overview

### Core Components

The plugin system consists of several key components:

```
plugins/
  your_plugin.py           # Main plugin file
  your_plugin/
    __init__.py           # Package initialization (optional, for modular plugins)
    components.py         # Sub-components (optional)
    config.py            # Configuration classes (optional)
```

### Discovery and Loading

Plugins are automatically discovered from two locations:

1. **Package directory**: `<package_root>/plugins/` (after `pip install`)
2. **Current working directory**: `./plugins/` (development mode)

The `PluginDiscovery` component scans these directories for Python modules containing plugin classes.

### Plugin Factory Pattern

The `PluginFactory` handles plugin instantiation with dependency injection:

```python
# Factory creates plugins with injected dependencies
plugin = factory.create_plugin(
    plugin_class=YourPlugin,
    name="your_plugin",
    event_bus=event_bus,
    renderer=renderer,
    config=config
)
```

### Required Plugin Methods

Every plugin must implement these methods:

```python
class YourPlugin:
    async def initialize(self, event_bus, config, **kwargs):
        """Called when plugin is loaded. Set up resources here.

        Args:
            event_bus: Application event bus for hook registration
            config: Configuration manager
            **kwargs: Additional dependencies (command_registry, llm_service, etc.)
        """

    async def register_hooks(self):
        """Register all hooks with the event bus."""

    async def shutdown(self):
        """Called when application exits. Clean up resources here."""

    def get_status_lines(self) -> Dict[str, List[str]]:
        """Return status lines for areas A, B, C of the status bar.

        Note: This is the legacy approach. Modern plugins should use
        _register_status_view() to register a StatusViewConfig instead.
        """

    @staticmethod
    def get_default_config() -> Dict[str, Any]:
        """Return default configuration dictionary."""
```

## Creating Your First Plugin

### Step 1: Create the Plugin File

Create a new file in the `plugins/` directory:

```bash
touch plugins/my_plugin.py
```

### Step 2: Basic Plugin Skeleton

```python
"""My Custom Plugin for Kollabor CLI.

A brief description of what your plugin does.
"""

import logging
from typing import Any, Dict, List

logger = logging.getLogger(__name__)


class MyPlugin:
    """A custom plugin that demonstrates the basics."""

    def __init__(self, name: str, event_bus, renderer, config):
        """Initialize the plugin with injected dependencies.

        Args:
            name: Plugin identifier (e.g., "my_plugin")
            event_bus: Event bus for hook registration
            renderer: Terminal renderer for UI operations
            config: Configuration manager
        """
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config

        # Plugin state
        self.enabled = True
        self.counter = 0

        logger.info(f"MyPlugin initialized: {name}")

    async def initialize(self, event_bus, config, **kwargs):
        """Initialize plugin resources.

        Args:
            event_bus: Application event bus
            config: Configuration manager
            **kwargs: Additional dependencies (command_registry, etc.)
        """
        logger.info(f"Starting {self.name} plugin...")
        # Set up resources, connections, etc.
        # Access command_registry via kwargs.get('command_registry')

    async def register_hooks(self):
        """Register hooks with the event bus."""
        logger.info(f"{self.name} has no hooks to register")

    async def shutdown(self):
        """Clean up plugin resources."""
        logger.info(f"Shutting down {self.name} plugin...")

    def get_status_lines(self) -> Dict[str, List[str]]:
        """Return status lines for the status bar.

        Returns:
            Dictionary with keys "A", "B", "C" for each status area.
        """
        return {
            "A": [],  # Core system status (left area)
            "B": [f"MyPlugin: {'On' if self.enabled else 'Off'}"],  # Middle area
            "C": []   # Detailed info (right area)
        }

    @staticmethod
    def get_default_config() -> Dict[str, Any]:
        """Return default configuration."""
        return {
            "plugins": {
                "my_plugin": {
                    "enabled": True,
                    "option1": "default_value"
                }
            }
        }
```

### Step 3: Plugin Naming Convention

The plugin name passed to `__init__` should match:
- The configuration key: `plugins.my_plugin.*`
- The Python class name (in snake_case): `MyPlugin` → `my_plugin`
- The filename: `my_plugin.py`

## Plugin Lifecycle

### Initialization Flow

```
1. PluginDiscovery scans plugins/ directory
2. PluginFactory instantiates plugin with dependencies
3. plugin.initialize() is called
4. plugin.register_hooks() is called
5. Plugin is now active
```

### Runtime Flow

```
6. Hooks receive events and execute callbacks
7. Commands are registered and can be executed
8. Status lines are rendered on each frame
9. get_status_lines() called repeatedly for display
```

### Shutdown Flow

```
10. plugin.shutdown() is called on application exit
11. Hooks are unregistered
12. Resources are cleaned up
```

### Lifecycle Method Best Practices

```python
class MyPlugin:
    async def initialize(self, event_bus, config, **kwargs):
        """Initialize - set up resources that persist for the session.

        Args:
            event_bus: Application event bus
            config: Configuration manager
            **kwargs: Additional dependencies (command_registry, etc.)
        """
        # Connect to external services
        # Start background tasks
        # Register commands via kwargs.get('command_registry')
        # Allocate resources
        pass

    async def shutdown(self):
        """Shutdown - clean up ALL resources to prevent leaks."""
        # Cancel background tasks
        # Close connections
        # Flush buffers
        # Release resources
        pass
```

## Dependency Injection

### Injected Dependencies

Plugins receive these dependencies via `__init__`:

```python
def __init__(self, name: str, event_bus, renderer, config):
```

| Parameter | Type | Purpose |
|-----------|------|---------|
| `name` | str | Plugin identifier for logging and config lookup |
| `event_bus` | EventBus | Register hooks and emit events |
| `renderer` | TerminalRenderer | Access terminal rendering, status system |
| `config` | ConfigManager | Access configuration values |

### Accessing Additional Dependencies

Some dependencies are available through `kwargs` during `initialize()`:

```python
async def initialize(self, event_bus, config, **kwargs):
    # Additional dependencies available through kwargs
    self.command_registry = kwargs.get('command_registry')
    self.plugin_factory = kwargs.get('plugin_factory')
    self.state_manager = kwargs.get('state_manager')
```

### Accessing Services via Renderer

```python
# Access the plugin factory through the renderer
if hasattr(self.renderer, 'plugin_factory'):
    factory = self.renderer.plugin_factory
    other_plugin = factory.get_instance("OtherPlugin")
```

## Registering Slash Commands

### Basic Command Registration

Commands are registered during plugin initialization:

```python
from core.events.models import CommandDefinition, CommandCategory

async def initialize(self, event_bus, config, **kwargs):
    # Get the command registry
    command_registry = kwargs.get('command_registry')
    if not command_registry:
        logger.warning("Command registry not available")
        return

    # Register a simple command
    command_def = CommandDefinition(
        name="mycommand",
        description="My custom command",
        handler=self._handle_my_command,
        plugin_name=self.name,
        category=CommandCategory.CUSTOM,
        aliases=["mc", "mycmd"]
    )
    command_registry.register_command(command_def)
```

### Command Handler Signature

```python
async def _handle_my_command(self, command: SlashCommand) -> CommandResult:
    """Handle the /mycommand command.

    Args:
        command: Parsed command with name, args, and metadata

    Returns:
        CommandResult with success status and message

    Note: Command handlers can also return str for simple messages,
    but CommandResult provides more control over display behavior.
    """
    from core.events.models import CommandResult

    try:
        # Access command arguments
        args = command.args  # List of argument strings

        # Do something with the arguments
        result = f"Processed: {' '.join(args)}"

        return CommandResult(
            success=True,
            message=result,
            display_type="info"
        )
    except Exception as e:
        return CommandResult(
            success=False,
            message=f"Error: {str(e)}",
            display_type="error"
        )
```

### Commands with Subcommands

Subcommands appear in the command menu when filtered to a single command:

```python
from core.events.models import CommandDefinition, SubcommandInfo, CommandCategory

async def initialize(self, event_bus, config, **kwargs):
    command_registry = kwargs.get('command_registry')

    command_def = CommandDefinition(
        name="files",
        description="File management commands",
        handler=self._handle_files_command,
        plugin_name=self.name,
        category=CommandCategory.FILE,
        subcommands=[
            # No-arg subcommand: executes immediately when selected
            SubcommandInfo("list", "", "List files in current directory"),

            # Arg subcommand: inserts text for user to complete
            SubcommandInfo("read", "<path>", "Read file contents"),
            SubcommandInfo("write", "<path>", "Write to a file"),
            SubcommandInfo("delete", "<path>", "Delete a file"),

            # Optional args shown in brackets
            SubcommandInfo("search", "[pattern]", "Search for files")
        ]
    )
    command_registry.register_command(command_def)
```

### Subcommand Handler Pattern

```python
async def _handle_files_command(self, command: SlashCommand) -> CommandResult:
    """Handle /files commands with subcommands."""
    from core.events.models import CommandResult

    args = command.args
    subcommand = args[0] if args else None

    if subcommand == "list":
        return await self._handle_files_list(command)

    elif subcommand == "read":
        if len(args) < 2:
            return CommandResult(
                success=False,
                message="Usage: /files read <path>",
                display_type="error"
            )
        return await self._handle_files_read(command)

    elif subcommand == "write":
        if len(args) < 2:
            return CommandResult(
                success=False,
                message="Usage: /files write <path>",
                display_type="error"
            )
        return await self._handle_files_write(command)

    # Show subcommands when no subcommand specified
    return CommandResult(
        success=True,
        message="Available subcommands: list, read, write, delete, search",
        display_type="info"
    )

async def _handle_files_list(self, command: SlashCommand) -> CommandResult:
    """Handle /files list."""
    import os

    files = os.listdir('.')
    return CommandResult(
        success=True,
        message=f"Files: {', '.join(files)}",
        display_type="info"
    )
```

## Hook System Integration

### Understanding Event Types

The hook system provides events for every aspect of application behavior:

```python
from core.events import EventType

# User interaction events
EventType.USER_INPUT_PRE      # Before input processing
EventType.USER_INPUT          # Main input processing
EventType.USER_INPUT_POST     # After input processing
EventType.KEY_PRESS_PRE       # Before key press handling
EventType.KEY_PRESS           # Main key press handling
EventType.KEY_PRESS_POST      # After key press handling
EventType.PASTE_DETECTED      # When paste is detected

# LLM events
EventType.LLM_REQUEST_PRE     # Before LLM API request
EventType.LLM_REQUEST         # Main LLM request
EventType.LLM_REQUEST_POST    # After LLM request
EventType.LLM_RESPONSE_PRE    # Before response processing
EventType.LLM_RESPONSE        # Main response processing
EventType.LLM_RESPONSE_POST   # After response processing
EventType.LLM_THINKING        # During LLM processing
EventType.CANCEL_REQUEST      # When request is cancelled

# Tool events
EventType.TOOL_CALL_PRE       # Before tool execution
EventType.TOOL_CALL           # Main tool execution
EventType.TOOL_CALL_POST      # After tool execution

# System events
EventType.SYSTEM_STARTUP      # Application startup
EventType.SYSTEM_SHUTDOWN     # Application shutdown
EventType.RENDER_FRAME        # Each render frame

# UI events
EventType.INPUT_RENDER_PRE    # Before input rendering
EventType.INPUT_RENDER        # Main input rendering
EventType.INPUT_RENDER_POST   # After input rendering
EventType.COMMAND_MENU_SHOW   # Command menu shown
EventType.COMMAND_MENU_NAVIGATE  # Command menu navigation
EventType.COMMAND_MENU_SELECT # Command menu selection
EventType.COMMAND_MENU_HIDE   # Command menu hidden
```

### Creating and Registering Hooks

```python
from core.events import Event, EventType, Hook, HookPriority

class MyPlugin:
    def __init__(self, name: str, event_bus, renderer, config):
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config

        # Create hooks during initialization
        self.hooks = self._create_hooks()

    def _create_hooks(self) -> List[Hook]:
        """Create all hooks for this plugin."""
        timeout = self.config.get('plugins.my_plugin.hook_timeout', 30)

        return [
            Hook(
                name="log_user_input",
                plugin_name=self.name,
                event_type=EventType.USER_INPUT_PRE,
                priority=HookPriority.PREPROCESSING.value,
                callback=self._log_user_input,
                timeout=timeout,
                error_action="continue"  # Continue on error
            ),
            Hook(
                name="transform_response",
                plugin_name=self.name,
                event_type=EventType.LLM_RESPONSE_POST,
                priority=HookPriority.POSTPROCESSING.value,
                callback=self._transform_response,
                timeout=timeout
            )
        ]

    async def register_hooks(self):
        """Register all hooks with the event bus."""
        for hook in self.hooks:
            await self.event_bus.register_hook(hook)
        logger.info(f"Registered {len(self.hooks)} hooks")

    async def _log_user_input(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
        """Hook callback: Log user input."""
        message = data.get('message', '')
        logger.info(f"User input: {message[:50]}...")
        return {"status": "logged"}

    async def _transform_response(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
        """Hook callback: Transform LLM response."""
        response = data.get('response', '')

        # Modify response data
        modified_response = response.replace("TODO", "[TODO]")

        # Return modified data
        return {
            "data": {"response": modified_response},
            "status": "transformed"
        }
```

### Hook Priorities

Use appropriate priorities to control execution order:

```python
from core.events import HookPriority

HookPriority.SYSTEM.value         # 1000 - Core system operations
HookPriority.SECURITY.value        # 900 - Security validation
HookPriority.PREPROCESSING.value   # 500 - Data transformation
HookPriority.LLM.value              # 100 - LLM operations
HookPriority.POSTPROCESSING.value   # 50 - Response formatting
HookPriority.DISPLAY.value           # 10 - UI updates
```

Higher numbers execute first. Choose the priority that matches your hook's purpose.

### Modifying Event Data

To modify data that flows through the event chain:

```python
async def _modify_input(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
    """Modify input data for downstream hooks."""
    # Modify the data dict directly
    if 'message' in data:
        data['message'] = data['message'].strip()

    # Or return modified data
    return {
        "data": data,  # This updates event.data
        "status": "modified"
    }
```

### Cancelling Events

Any hook can cancel the entire event chain:

```python
async def _validate_input(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
    """Validate input and cancel if invalid."""
    message = data.get('message', '')

    if not message.strip():
        event.cancelled = True
        return {"status": "cancelled", "reason": "empty_message"}

    return {"status": "valid"}
```

## Status Bar Integration

### Status Areas

The status bar has three areas:

```
[A] Core System      [B] Plugin Metrics    [C] Detailed Info
```

- **Area A**: Core system status (left)
- **Area B**: Plugin-specific metrics (middle)
- **Area C**: Detailed information (right)

### Providing Status Lines

```python
def get_status_lines(self) -> Dict[str, List[str]]:
    """Return status lines for each area.

    Returns:
        Dict with keys "A", "B", "C" mapping to lists of status strings.
    """
    enabled = self.config.get('plugins.my_plugin.enabled', True)

    return {
        "A": [],  # Typically empty for plugins
        "B": [    # Plugin metrics go here
            f"MyPlugin: {'On' if enabled else 'Off'}",
            f"Processed: {self.counter}"
        ],
        "C": [    # Detailed info
            f"Last: {self.last_operation}"
        ] if enabled else []
    }
```

### Conditional Status Display

```python
def get_status_lines(self) -> Dict[str, List[str]]:
    """Conditionally show status based on configuration."""
    # Check if status display is enabled
    show_status = self.config.get('plugins.my_plugin.show_status', True)
    if not show_status:
        return {"A": [], "B": [], "C": []}

    enabled = self.config.get('plugins.my_plugin.enabled', False)

    if not enabled:
        return {"A": [], "B": ["MyPlugin: Off"], "C": []}

    # Detailed status when enabled
    return {
        "A": [],
        "B": [
            f"MyPlugin: {self.status}",
            f"Items: {self.item_count}"
        ],
        "C": [
            f"Rate: {self.rate}/s",
            f"Errors: {self.error_count}"
        ]
    }
```

### Advanced: Status View Registration

For complex status displays, register a custom status view:

```python
async def initialize(self, event_bus, config, **kwargs):
    """Initialize and register custom status view.

    Args:
        event_bus: Application event bus
        config: Configuration manager
        **kwargs: Additional dependencies
    """
    # ... other initialization ...

    await self._register_status_view()

async def _register_status_view(self):
    """Register a custom status view with the status renderer."""
    try:
        from core.io.status_renderer import StatusViewConfig, BlockConfig

        # Check if status registry is available
        if (hasattr(self.renderer, 'status_renderer') and
            self.renderer.status_renderer and
            hasattr(self.renderer.status_renderer, 'status_registry')):

            view = StatusViewConfig(
                name="My Plugin View",
                plugin_source="my_plugin",
                priority=300,  # Lower number = higher priority
                blocks=[
                    BlockConfig(
                        width_fraction=1.0,
                        content_provider=self._get_status_content,
                        title="My Plugin",
                        priority=100
                    )
                ]
            )

            registry = self.renderer.status_renderer.status_registry
            registry.register_status_view("my_plugin", view)
            logger.info("Registered custom status view")

    except Exception as e:
        logger.error(f"Failed to register status view: {e}")

def _get_status_content(self) -> List[str]:
    """Provide content for the status view."""
    return [
        f"Status: {self.status}",
        f"Count: {self.counter}",
        f"Errors: {self.error_count}"
    ]
```

## Configuration Integration

### Default Configuration

Every plugin should provide default configuration:

```python
@staticmethod
def get_default_config() -> Dict[str, Any]:
    """Return default configuration for this plugin."""
    return {
        "plugins": {
            "my_plugin": {
                # Basic settings
                "enabled": True,
                "show_status": True,

                # Feature flags
                "enable_feature_x": False,
                "enable_feature_y": True,

                # Numeric settings
                "timeout": 30,
                "max_retries": 3,

                # Nested settings
                "advanced": {
                    "debug_mode": False,
                    "log_level": "INFO"
                }
            }
        }
    }
```

### Reading Configuration

Use dot notation to access configuration values:

```python
async def initialize(self, event_bus, config, **kwargs):
    """Initialize plugin with configuration.

    Args:
        event_bus: Application event bus
        config: Configuration manager
        **kwargs: Additional dependencies
    """
    # Note: config is passed as parameter, update self.config if needed
    self.config = config

    # Direct config access
    enabled = self.config.get('plugins.my_plugin.enabled', True)
    timeout = self.config.get('plugins.my_plugin.timeout', 30)

    # Nested config access
    debug_mode = self.config.get('plugins.my_plugin.advanced.debug_mode', False)

    logger.info(f"Plugin enabled: {enabled}, timeout: {timeout}s")
```

### Configuration Helper Class

For complex plugins, create a dedicated config class:

```python
# plugins/my_plugin/config.py
from dataclasses import dataclass
from typing import Optional

@dataclass
class MyPluginConfig:
    """Configuration for MyPlugin."""
    enabled: bool = True
    show_status: bool = True
    timeout: int = 30
    max_retries: int = 3
    debug_mode: bool = False

    @classmethod
    def from_config_manager(cls, config) -> 'MyPluginConfig':
        """Create instance from ConfigManager."""
        prefix = 'plugins.my_plugin'
        return cls(
            enabled=config.get(f'{prefix}.enabled', True),
            show_status=config.get(f'{prefix}.show_status', True),
            timeout=config.get(f'{prefix}.timeout', 30),
            max_retries=config.get(f'{prefix}.max_retries', 3),
            debug_mode=config.get(f'{prefix}.advanced.debug_mode', False)
        )

# plugins/my_plugin.py
class MyPlugin:
    def __init__(self, name: str, event_bus, renderer, config):
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config

        # Use config helper
        from .config import MyPluginConfig
        self.settings = MyPluginConfig.from_config_manager(config)
```

### Configuration Widgets

Provide UI widgets for configuration:

```python
@staticmethod
def get_config_widgets() -> Dict[str, Any]:
    """Get configuration widget definitions for the config modal."""
    return {
        "title": "My Plugin Settings",
        "widgets": [
            {
                "type": "checkbox",
                "label": "Enable Plugin",
                "config_path": "plugins.my_plugin.enabled",
                "help": "Toggle the plugin on or off"
            },
            {
                "type": "checkbox",
                "label": "Show Status",
                "config_path": "plugins.my_plugin.show_status",
                "help": "Display plugin status in the status bar"
            },
            {
                "type": "slider",
                "label": "Timeout",
                "config_path": "plugins.my_plugin.timeout",
                "min_value": 1,
                "max_value": 120,
                "step": 1,
                "help": "Timeout in seconds"
            },
            {
                "type": "slider",
                "label": "Max Retries",
                "config_path": "plugins.my_plugin.max_retries",
                "min_value": 0,
                "max_value": 10,
                "step": 1,
                "help": "Maximum number of retry attempts"
            }
        ]
    }
```

## Complete Working Example

### Echo Command Plugin

Here's a complete, working plugin that implements an `/echo` command with subcommands:

```python
"""Echo Plugin for Kollabor CLI.

A simple demonstration plugin that echoes back user input
in various formats. This plugin demonstrates:
- Slash command registration
- Subcommand handling
- Hook integration
- Status bar integration
- Configuration management
"""

import logging
from typing import Any, Dict, List

from core.events import Event, EventType, Hook, HookPriority
from core.events.models import (
    CommandDefinition, CommandCategory, SubcommandInfo,
    SlashCommand, CommandResult
)

logger = logging.getLogger(__name__)


class EchoPlugin:
    """A plugin that echoes user input in various formats."""

    def __init__(self, name: str, event_bus, renderer, config):
        """Initialize the Echo plugin.

        Args:
            name: Plugin identifier
            event_bus: Event bus for hook registration
            renderer: Terminal renderer
            config: Configuration manager
        """
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config

        # Plugin state
        self.echo_count = 0
        self.last_echo = ""

        # Create hooks
        self.hooks = self._create_hooks()

        logger.info(f"EchoPlugin initialized: {name}")

    def _create_hooks(self) -> List[Hook]:
        """Create hooks for the plugin."""
        timeout = self.config.get('plugins.echo.hook_timeout', 10)

        return [
            Hook(
                name="echo_log_input",
                plugin_name=self.name,
                event_type=EventType.USER_INPUT_POST,
                priority=HookPriority.POSTPROCESSING.value,
                callback=self._log_user_input,
                timeout=timeout,
                error_action="continue"
            )
        ]

    async def initialize(self, event_bus, config, **kwargs):
        """Initialize the plugin.

        Args:
            event_bus: Event bus instance
            config: Configuration manager
            **kwargs: Additional dependencies (command_registry, etc.)
        """
        logger.info("Starting Echo plugin...")

        # Register slash commands
        command_registry = kwargs.get('command_registry')
        if command_registry:
            self._register_commands(command_registry)

    def _register_commands(self, command_registry):
        """Register slash commands.

        Args:
            command_registry: Command registry instance
        """
        # Main echo command with subcommands
        echo_command = CommandDefinition(
            name="echo",
            description="Echo back your input in various formats",
            handler=self._handle_echo_command,
            plugin_name=self.name,
            category=CommandCategory.CUSTOM,
            aliases=["repeat", "say"],
            subcommands=[
                SubcommandInfo("text", "<message>", "Echo as plain text"),
                SubcommandInfo("upper", "<message>", "Echo as UPPERCASE"),
                SubcommandInfo("lower", "<message>", "Echo as lowercase"),
                SubcommandInfo("reverse", "<message>", "Echo reversed"),
                SubcommandInfo("stats", "", "Show echo statistics"),
            ]
        )
        command_registry.register_command(echo_command)
        logger.info("Registered /echo command")

    async def register_hooks(self):
        """Register hooks with the event bus."""
        for hook in self.hooks:
            await self.event_bus.register_hook(hook)
        logger.info(f"Registered {len(self.hooks)} hooks")

    async def shutdown(self):
        """Clean up plugin resources."""
        logger.info(f"Echo plugin shutting down. Total echos: {self.echo_count}")

    # ========== Command Handlers ==========

    async def _handle_echo_command(self, command: SlashCommand) -> CommandResult:
        """Handle /echo commands.

        Args:
            command: Parsed command with name and arguments

        Returns:
            CommandResult with success status and message
        """
        args = command.args

        if not args:
            # Show usage when no arguments provided
            return CommandResult(
                success=True,
                message="Available subcommands: text, upper, lower, reverse, stats",
                display_type="info"
            )

        subcommand = args[0].lower()

        # Route to appropriate handler
        handlers = {
            "text": self._echo_text,
            "upper": self._echo_upper,
            "lower": self._echo_lower,
            "reverse": self._echo_reverse,
            "stats": self._echo_stats,
        }

        handler = handlers.get(subcommand)
        if handler:
            return await handler(command)

        # Unknown subcommand
        return CommandResult(
            success=False,
            message=f"Unknown subcommand: {subcommand}",
            display_type="error"
        )

    async def _echo_text(self, command: SlashCommand) -> CommandResult:
        """Echo text as-is."""
        message = ' '.join(command.args[1:]) if len(command.args) > 1 else ""

        if not message:
            return CommandResult(
                success=False,
                message="Usage: /echo text <message>",
                display_type="error"
            )

        self.echo_count += 1
        self.last_echo = message

        return CommandResult(
            success=True,
            message=message,
            display_type="info"
        )

    async def _echo_upper(self, command: SlashCommand) -> CommandResult:
        """Echo text as uppercase."""
        message = ' '.join(command.args[1:]) if len(command.args) > 1 else ""

        if not message:
            return CommandResult(
                success=False,
                message="Usage: /echo upper <message>",
                display_type="error"
            )

        self.echo_count += 1
        self.last_echo = message.upper()

        return CommandResult(
            success=True,
            message=message.upper(),
            display_type="info"
        )

    async def _echo_lower(self, command: SlashCommand) -> CommandResult:
        """Echo text as lowercase."""
        message = ' '.join(command.args[1:]) if len(command.args) > 1 else ""

        if not message:
            return CommandResult(
                success=False,
                message="Usage: /echo lower <message>",
                display_type="error"
            )

        self.echo_count += 1
        self.last_echo = message.lower()

        return CommandResult(
            success=True,
            message=message.lower(),
            display_type="info"
        )

    async def _echo_reverse(self, command: SlashCommand) -> CommandResult:
        """Echo text reversed."""
        message = ' '.join(command.args[1:]) if len(command.args) > 1 else ""

        if not message:
            return CommandResult(
                success=False,
                message="Usage: /echo reverse <message>",
                display_type="error"
            )

        reversed_msg = message[::-1]
        self.echo_count += 1
        self.last_echo = reversed_msg

        return CommandResult(
            success=True,
            message=reversed_msg,
            display_type="info"
        )

    async def _echo_stats(self, command: SlashCommand) -> CommandResult:
        """Show echo statistics."""
        return CommandResult(
            success=True,
            message=f"Echo stats: {self.echo_count} echos performed. Last: '{self.last_echo}'",
            display_type="info"
        )

    # ========== Hook Callbacks ==========

    async def _log_user_input(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
        """Log user input after processing.

        Args:
            data: Event data
            event: Event object

        Returns:
            Hook result
        """
        message = data.get('message', '')
        if message.startswith('/echo'):
            logger.debug(f"Echo command detected: {message}")

        return {"status": "logged"}

    # ========== Status Bar ==========

    def get_status_lines(self) -> Dict[str, List[str]]:
        """Return status lines for the status bar.

        Returns:
            Dict with keys "A", "B", "C" for each status area
        """
        enabled = self.config.get('plugins.echo.enabled', True)
        show_stats = self.config.get('plugins.echo.show_stats_in_status', True)

        if not enabled:
            return {"A": [], "B": [], "C": []}

        if not show_stats:
            return {
                "A": [],
                "B": ["Echo: Active"],
                "C": []
            }

        return {
            "A": [],
            "B": [
                f"Echo: {self.echo_count}",
            ],
            "C": [
                f"Last: {self.last_echo[:20]}..." if len(self.last_echo) > 20 else f"Last: {self.last_echo}"
            ] if self.last_echo else []
        }

    # ========== Configuration ==========

    @staticmethod
    def get_default_config() -> Dict[str, Any]:
        """Return default configuration for Echo plugin.

        Returns:
            Default configuration dictionary
        """
        return {
            "plugins": {
                "echo": {
                    "enabled": True,
                    "show_stats_in_status": True,
                    "hook_timeout": 10
                }
            }
        }

    @staticmethod
    def get_config_widgets() -> Dict[str, Any]:
        """Get configuration widget definitions.

        Returns:
            Widget definitions for the config modal
        """
        return {
            "title": "Echo Plugin",
            "widgets": [
                {
                    "type": "checkbox",
                    "label": "Enable Plugin",
                    "config_path": "plugins.echo.enabled",
                    "help": "Enable the echo plugin"
                },
                {
                    "type": "checkbox",
                    "label": "Show Stats in Status",
                    "config_path": "plugins.echo.show_stats_in_status",
                    "help": "Display echo statistics in status bar"
                },
                {
                    "type": "slider",
                    "label": "Hook Timeout",
                    "config_path": "plugins.echo.hook_timeout",
                    "min_value": 1,
                    "max_value": 60,
                    "step": 1,
                    "help": "Hook execution timeout in seconds"
                }
            ]
        }
```

### Usage Examples

Once installed, the Echo plugin provides these commands:

```
/echo text Hello World
  Output: Hello World

/echo upper Hello World
  Output: HELLO WORLD

/echo lower Hello World
  Output: hello world

/echo reverse Hello World
  Output: dlroW olleH

/echo stats
  Output: Echo stats: 4 echos performed. Last: 'dlroW olleH'
```

## Testing Strategies

### Unit Tests

Create unit tests for individual plugin methods:

```python
# tests/test_echo_plugin.py
import unittest
from unittest.mock import Mock, MagicMock, AsyncMock
from plugins.echo_plugin import EchoPlugin

class TestEchoPlugin(unittest.TestCase):
    """Unit tests for Echo plugin."""

    def setUp(self):
        """Set up test fixtures."""
        self.event_bus = Mock()
        self.renderer = Mock()
        self.config = Mock()

        # Configure mock config
        self.config.get = Mock(side_effect=lambda key, default=None: {
            'plugins.echo.enabled': True,
            'plugins.echo.show_stats_in_status': True,
            'plugins.echo.hook_timeout': 10
        }.get(key, default))

        self.plugin = EchoPlugin(
            name="echo",
            event_bus=self.event_bus,
            renderer=self.renderer,
            config=self.config
        )

    def test_initialization(self):
        """Test plugin initializes correctly."""
        self.assertEqual(self.plugin.name, "echo")
        self.assertEqual(self.plugin.echo_count, 0)
        self.assertEqual(self.plugin.last_echo, "")

    def test_status_lines(self):
        """Test status line generation."""
        status = self.plugin.get_status_lines()

        self.assertIn("A", status)
        self.assertIn("B", status)
        self.assertIn("C", status)
        self.assertEqual(len(status["B"]), 1)
        self.assertTrue("Echo:" in status["B"][0])

    def test_default_config(self):
        """Test default configuration structure."""
        config = EchoPlugin.get_default_config()

        self.assertIn("plugins", config)
        self.assertIn("echo", config["plugins"])
        self.assertTrue(config["plugins"]["echo"]["enabled"])

    async def test_echo_text_handler(self):
        """Test echo text command handler."""
        from core.events.models import SlashCommand

        command = SlashCommand(
            name="echo",
            args=["text", "Hello", "World"],
            raw_input="/echo text Hello World"
        )

        result = await self.plugin._echo_text(command)

        self.assertTrue(result.success)
        self.assertEqual(result.message, "Hello World")
        self.assertEqual(self.plugin.echo_count, 1)

    async def test_echo_upper_handler(self):
        """Test echo upper command handler."""
        from core.events.models import SlashCommand

        command = SlashCommand(
            name="echo",
            args=["upper", "Hello"],
            raw_input="/echo upper Hello"
        )

        result = await self.plugin._echo_upper(command)

        self.assertTrue(result.success)
        self.assertEqual(result.message, "HELLO")

    async def test_echo_reverse_handler(self):
        """Test echo reverse command handler."""
        from core.events.models import SlashCommand

        command = SlashCommand(
            name="echo",
            args=["reverse", "abc"],
            raw_input="/echo reverse abc"
        )

        result = await self.plugin._echo_reverse(command)

        self.assertTrue(result.success)
        self.assertEqual(result.message, "cba")

    def test_config_widgets(self):
        """Test configuration widget definitions."""
        widgets = EchoPlugin.get_config_widgets()

        self.assertIn("title", widgets)
        self.assertIn("widgets", widgets)
        self.assertTrue(len(widgets["widgets"]) > 0)

if __name__ == '__main__':
    unittest.main()
```

### Integration Tests

Test plugin integration with the application:

```python
# tests/integration/test_echo_plugin_integration.py
import unittest
from unittest.mock import Mock, AsyncMock
from core.events import EventType
from plugins.echo_plugin import EchoPlugin

class TestEchoPluginIntegration(unittest.TestCase):
    """Integration tests for Echo plugin."""

    async def test_hook_registration(self):
        """Test hooks are registered correctly."""
        event_bus = Mock()
        event_bus.register_hook = AsyncMock()

        plugin = EchoPlugin(
            name="echo",
            event_bus=event_bus,
            renderer=Mock(),
            config=Mock()
        )

        await plugin.register_hooks()

        # Verify hooks were registered
        self.assertTrue(event_bus.register_hook.called)
        call_count = event_bus.register_hook.call_count
        self.assertEqual(call_count, len(plugin.hooks))

    async def test_hook_execution(self):
        """Test hook executes on events."""
        from core.events import Event

        plugin = EchoPlugin(
            name="echo",
            event_bus=Mock(),
            renderer=Mock(),
            config=Mock()
        )

        # Create a mock event
        event = Event(
            type=EventType.USER_INPUT_POST,
            source="test",
            data={"message": "/echo text Hello"}
        )

        # Execute the hook callback
        result = await plugin._log_user_input(event.data, event)

        self.assertEqual(result["status"], "logged")
```

### Manual Testing Checklist

Test your plugin manually with this checklist:

- [ ] Plugin loads without errors
- [ ] `initialize()` completes successfully
- [ ] `register_hooks()` registers all hooks
- [ ] Commands appear in `/help` output
- [ ] Command menu shows commands when typing `/`
- [ ] Subcommands appear when filtering to single command
- [ ] Status lines appear in status bar
- [ ] Configuration widgets appear in `/config`
- [ ] Hooks execute on appropriate events
- [ ] `shutdown()` cleans up resources
- [ ] Plugin works in pipe mode (`kollab -p`)
- [ ] Error handling works correctly
- [ ] Configuration changes take effect

### Debug Logging

Enable debug logging for your plugin:

```python
import logging

# Enable debug logging
logging.getLogger('plugins.echo_plugin').setLevel(logging.DEBUG)
```

Or via configuration:

```python
@staticmethod
def get_default_config() -> Dict[str, Any]:
    return {
        "plugins": {
            "echo": {
                "debug_logging": True,
                "log_all_events": True
            }
        }
    }
```

## Best Practices

### 1. Error Handling

Always handle errors gracefully:

```python
async def _handle_command(self, command: SlashCommand) -> CommandResult:
    """Handle command with proper error handling."""
    try:
        # Validate inputs
        if not command.args:
            return CommandResult(
                success=False,
                message="Usage: /mycommand <args>",
                display_type="error"
            )

        # Process command
        result = await self._process(command.args)

        return CommandResult(
            success=True,
            message=result,
            display_type="info"
        )

    except ValueError as e:
        return CommandResult(
            success=False,
            message=f"Invalid input: {e}",
            display_type="error"
        )
    except Exception as e:
        logger.error(f"Command error: {e}", exc_info=True)
        return CommandResult(
            success=False,
            message="An unexpected error occurred",
            display_type="error"
        )
```

### 2. Async/Await Patterns

Use async patterns consistently:

```python
# Good: Use async for I/O operations
async def _fetch_data(self, url: str) -> str:
    """Fetch data asynchronously."""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

# Good: Use async for plugin methods
async def initialize(self):
    """Initialize asynchronously."""
    await self._connect_to_service()
    await self._load_data()

# Avoid: Don't block with synchronous I/O
async def initialize(self):
    # Bad: Blocks the event loop
    data = requests.get(url)  # Don't do this!
```

### 3. Resource Management

Clean up resources properly:

```python
class MyPlugin:
    def __init__(self, name: str, event_bus, renderer, config):
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config

        # Track resources for cleanup
        self.background_tasks = []
        self.connections = []

    async def initialize(self):
        """Initialize and track resources."""
        # Start background tasks
        task = asyncio.create_task(self._background_worker())
        self.background_tasks.append(task)

        # Open connections
        conn = await self._open_connection()
        self.connections.append(conn)

    async def shutdown(self):
        """Clean up all tracked resources."""
        # Cancel background tasks
        for task in self.background_tasks:
            if not task.done():
                task.cancel()
                try:
                    await task
                except asyncio.CancelledError:
                    pass

        # Close connections
        for conn in self.connections:
            await conn.close()

        logger.info(f"{self.name} shutdown complete")
```

### 4. Configuration Design

Organize configuration logically:

```python
@staticmethod
def get_default_config() -> Dict[str, Any]:
    """Provide well-organized default configuration."""
    return {
        "plugins": {
            "my_plugin": {
                # Enable/disable
                "enabled": True,
                "show_status": True,
                "debug_logging": False,

                # Feature flags
                "features": {
                    "feature_a": True,
                    "feature_b": False
                },

                # Numeric settings
                "limits": {
                    "timeout": 30,
                    "max_retries": 3,
                    "buffer_size": 1024
                },

                # Connection settings
                "connection": {
                    "host": "localhost",
                    "port": 8080,
                    "ssl": True
                }
            }
        }
    }
```

### 5. Status Bar Guidelines

Follow status bar conventions:

```python
def get_status_lines(self) -> Dict[str, List[str]]:
    """Return appropriately formatted status lines."""
    # Area A: Usually empty for plugins (reserved for core)
    area_a = []

    # Area B: Plugin name and key metrics
    area_b = [
        f"{self.plugin_name}: {self.status}",
    ]

    # Area C: Detailed information (only when relevant)
    area_c = []
    if self.show_details:
        area_c.append(f"Count: {self.count}")
        area_c.append(f"Rate: {self.rate}/s")

    return {"A": area_a, "B": area_b, "C": area_c}
```

### 6. Hook Best Practices

Follow hook conventions:

```python
# 1. Use appropriate priorities
Hook(
    name="preprocess_input",
    priority=HookPriority.PREPROCESSING.value,  # For data transformation
    callback=self._preprocess
)

# 2. Set reasonable timeouts
Hook(
    name="slow_operation",
    timeout=60,  # Longer timeout for slow operations
    callback=self._slow_handler
)

# 3. Handle errors gracefully
Hook(
    name="robust_handler",
    error_action="continue",  # Don't stop the chain on error
    callback=self._robust_callback
)

# 4. Always return a result
async def _hook_callback(self, data: Dict, event: Event) -> Dict:
    try:
        # Process data
        return {"status": "success", "data": modified_data}
    except Exception as e:
        logger.error(f"Hook error: {e}")
        return {"status": "error", "error": str(e)}
```

## Troubleshooting

### Plugin Not Loading

**Symptoms**: Plugin doesn't appear in `/help` or status bar

**Solutions**:

1. Check file location:
   ```bash
   # Should be in one of these locations:
   ls plugins/your_plugin.py
   ls path/to/package/plugins/your_plugin.py
   ```

2. Check for syntax errors:
   ```bash
   python -m py_compile plugins/your_plugin.py
   ```

3. Check the logs:
   ```bash
   tail -f ~/.kollabor-cli/projects/*/logs/kollabor.log | grep your_plugin
   ```

4. Verify required methods exist:
   - `initialize()`
   - `register_hooks()`
   - `shutdown()`
   - `get_status_lines()`
   - `get_default_config()`

### Commands Not Appearing

**Symptoms**: Command doesn't show in `/help` or command menu

**Solutions**:

1. Verify command registry is available:
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       command_registry = kwargs.get('command_registry')
       if not command_registry:
           logger.error("Command registry not available")
           return
   ```

2. Check command name doesn't conflict:
   ```bash
   # List all commands to check for conflicts
   kollab  # Then type /help
   ```

3. Verify category is valid:
   ```python
   from core.events.models import CommandCategory
   # Use: SYSTEM, CONVERSATION, AGENT, DEVELOPMENT, FILE, TASK, CUSTOM
   ```

### Hooks Not Executing

**Symptoms**: Hook callbacks never run

**Solutions**:

1. Verify event type is correct:
   ```python
   # Check EventType in core/events/models.py
   from core.events import EventType
   ```

2. Check plugin is enabled:
   ```python
   enabled = self.config.get('plugins.my_plugin.enabled', True)
   ```

3. Verify hooks are registered:
   ```python
   async def register_hooks(self):
       for hook in self.hooks:
           await self.event_bus.register_hook(hook)
           logger.info(f"Registered hook: {hook.name}")
   ```

4. Check hook priority - higher priority hooks execute first

### Status Not Showing

**Symptoms**: Status lines don't appear in status bar

**Solutions**:

1. Check configuration:
   ```python
   show_status = self.config.get('plugins.my_plugin.show_status', True)
   ```

2. Verify return format:
   ```python
   def get_status_lines(self) -> Dict[str, List[str]]:
       return {"A": [], "B": ["status"], "C": []}
   ```

3. Check area isn't being crowded by other plugins

4. Verify plugin is enabled

### Import Errors

**Symptoms**: `ImportError` or `ModuleNotFoundError` when loading plugin

**Solutions**:

1. Use relative imports for sub-modules:
   ```python
   # In plugins/your_plugin/__init__.py
   from .components import MyComponent
   ```

2. Add parent directory to path for development:
   ```python
   import sys
   from pathlib import Path
   sys.path.insert(0, str(Path(__file__).parent.parent))
   ```

3. Check for circular imports between plugins

## Next Steps

Now that you understand plugin development:

1. Explore existing plugins for more patterns:
   - `plugins/hook_monitoring_plugin.py` - Comprehensive monitoring example
   - `plugins/enhanced_input_plugin.py` - UI enhancement patterns
   - `plugins/save_conversation_plugin.py` - Command with subcommands example

2. Read additional documentation:
   - `docs/reference/hook-system-sdk.md` - Complete hook system reference
   - `docs/reference/slash-commands-guide.md` - Slash command patterns
   - `docs/reference/` - All reference documentation

3. Join the community and share your plugins!

---

For additional help, refer to the main documentation or open an issue on GitHub.
