# Plugin CLI Arguments Registration

Allow plugins to register custom CLI arguments that are parsed before app initialization.

## Core Concept

Plugins define CLI args via static method -> Discovery loads them early -> Args parsed -> Plugins handle them

## Current Flow (Problem)

```
cli.py: parse_arguments()
            |
            v
TerminalLLMChat.__init__()
            |
            v
PluginDiscovery.discover()
            |
            v
PluginFactory.create_all()
            |
            v
plugin.initialize()
```

Plugins load AFTER args are parsed. No way to add custom args.

## New Flow

```
cli.py: discover_plugin_args()     # NEW: early plugin scan
            |
            v
cli.py: parse_arguments(plugins)   # include plugin args
            |
            v
cli.py: handle_early_args(args)    # NEW: plugins can exit early (--capture)
            |
            v
TerminalLLMChat.__init__(args)
            |
            v
PluginFactory.create_all(args)     # pass args to plugins
            |
            v
plugin.initialize(args)            # plugins see their args
```

## Plugin API

### Static Arg Registration

```python
class AgentOrchestratorPlugin(BasePlugin):
    """Plugin that manages sub-agents in tmux sessions."""

    @staticmethod
    def register_cli_args(parser: argparse.ArgumentParser) -> None:
        """Register CLI arguments. Called before app init."""
        group = parser.add_argument_group("Agent Orchestrator")
        group.add_argument(
            "--session",
            type=str,
            metavar="NAME",
            help="Attach to or resume agent session"
        )
        group.add_argument(
            "--capture",
            type=int,
            metavar="LINES",
            help="Capture N lines from session output (requires --session)"
        )

    @staticmethod
    def handle_early_args(args: argparse.Namespace) -> bool:
        """Handle args that should exit early. Return True to exit."""
        if args.session and args.capture:
            # Capture mode: print output and exit immediately
            AgentOrchestratorPlugin._capture_and_exit(args.session, args.capture)
            return True  # signal exit
        return False

    @staticmethod
    def _capture_and_exit(session: str, lines: int) -> None:
        """Capture session output and print to stdout."""
        import subprocess
        project = Path.cwd().name
        full_session = f"{project}-{session}"

        result = subprocess.run(
            ["tmux", "capture-pane", "-t", full_session, "-p", "-S", f"-{lines}"],
            capture_output=True,
            text=True
        )
        print(result.stdout)
        sys.exit(0)

    def initialize(self, args: argparse.Namespace = None) -> None:
        """Normal initialization with access to parsed args."""
        if args and args.session:
            self.active_session = args.session
```

### BasePlugin Changes

```python
class BasePlugin:
    """Base class for all plugins."""

    @staticmethod
    def register_cli_args(parser: argparse.ArgumentParser) -> None:
        """Override to register custom CLI arguments.

        Called during early plugin discovery, before app initialization.
        Use parser.add_argument_group() to organize args.
        """
        pass

    @staticmethod
    def handle_early_args(args: argparse.Namespace) -> bool:
        """Override to handle args that should exit before app starts.

        Return True to exit immediately (e.g., --capture mode).
        Return False to continue normal startup.
        """
        return False

    def initialize(self, args: argparse.Namespace = None) -> None:
        """Initialize plugin. Args are now available."""
        pass
```

## Implementation

### cli.py Changes

```python
def discover_plugin_args() -> list:
    """Discover plugins and collect their CLI arg registrations."""
    from .plugins.discovery import PluginDiscovery

    discovery = PluginDiscovery()
    plugin_classes = discovery.discover_classes_only()  # NEW: lightweight discovery

    return plugin_classes


def parse_arguments(plugin_classes: list = None):
    """Parse CLI arguments including plugin-registered args."""
    parser = argparse.ArgumentParser(...)

    # Core args (existing)
    parser.add_argument("-v", "--version", ...)
    parser.add_argument("-p", "--pipe", ...)
    # ... etc

    # Plugin args
    if plugin_classes:
        for plugin_class in plugin_classes:
            if hasattr(plugin_class, 'register_cli_args'):
                try:
                    plugin_class.register_cli_args(parser)
                except Exception as e:
                    logging.warning(f"Plugin {plugin_class.__name__} arg registration failed: {e}")

    return parser.parse_args()


def handle_early_plugin_args(args, plugin_classes: list) -> bool:
    """Let plugins handle args that should exit early."""
    for plugin_class in plugin_classes:
        if hasattr(plugin_class, 'handle_early_args'):
            try:
                if plugin_class.handle_early_args(args):
                    return True  # plugin requested exit
            except Exception as e:
                logging.error(f"Plugin {plugin_class.__name__} early arg handler failed: {e}")
    return False


async def async_main() -> None:
    """Main entry point."""
    # Early plugin discovery for CLI args
    plugin_classes = discover_plugin_args()

    # Parse all args (core + plugin)
    args = parse_arguments(plugin_classes)

    # Handle early-exit args (like --capture)
    if handle_early_plugin_args(args, plugin_classes):
        return  # plugin handled it, exit

    # Normal app startup
    app = TerminalLLMChat(args=args, ...)
    await app.start()
```

### PluginDiscovery Changes

```python
class PluginDiscovery:

    def discover_classes_only(self) -> list:
        """Lightweight discovery that only loads plugin classes.

        Does NOT instantiate plugins. Used for CLI arg registration.
        """
        plugin_classes = []

        for plugin_path in self._find_plugin_files():
            try:
                module = self._load_module(plugin_path)
                for cls in self._find_plugin_classes(module):
                    plugin_classes.append(cls)
            except Exception as e:
                logging.warning(f"Failed to load plugin {plugin_path}: {e}")

        return plugin_classes
```

## Example Plugins

### Agent Orchestrator (--session, --capture)

```python
class AgentOrchestratorPlugin(BasePlugin):
    @staticmethod
    def register_cli_args(parser):
        group = parser.add_argument_group("Agent Orchestrator")
        group.add_argument("--session", type=str)
        group.add_argument("--capture", type=int)

    @staticmethod
    def handle_early_args(args):
        if args.session and args.capture:
            # print session output and exit
            return True
        return False
```

### Debug Plugin (--debug-hooks)

```python
class DebugPlugin(BasePlugin):
    @staticmethod
    def register_cli_args(parser):
        group = parser.add_argument_group("Debug")
        group.add_argument("--debug-hooks", action="store_true",
                          help="Log all hook executions")

    def initialize(self, args=None):
        if args and args.debug_hooks:
            self.enable_hook_logging()
```

### MCP Plugin (--mcp-server)

```python
class MCPPlugin(BasePlugin):
    @staticmethod
    def register_cli_args(parser):
        group = parser.add_argument_group("MCP")
        group.add_argument("--mcp-server", type=str, action="append",
                          help="Additional MCP server to load")

    def initialize(self, args=None):
        if args and args.mcp_server:
            for server in args.mcp_server:
                self.load_server(server)
```

## Help Output

```
$ kollab --help

usage: kollab [-h] [-v] [-p] [--timeout TIMEOUT] ...

Kollab - Terminal-based LLM chat interface

options:
  -h, --help            show this help message and exit
  -v, --version         show version number and exit
  -p, --pipe            pipe mode
  --timeout TIMEOUT     timeout for pipe mode
  ...

Agent Orchestrator:
  --session NAME        attach to or resume agent session
  --capture LINES       capture N lines from session output

Debug:
  --debug-hooks         log all hook executions

MCP:
  --mcp-server SERVER   additional MCP server to load (can repeat)
```

## Files to Modify

```
core/
├── cli.py                    # add discover_plugin_args(), modify parse_arguments()
├── plugins/
│   ├── base.py              # add register_cli_args(), handle_early_args() stubs
│   └── discovery.py         # add discover_classes_only()
└── application.py           # accept args parameter, pass to plugins
```

## Config

No config needed. Plugin args are code-defined.

## Error Handling

- Plugin arg registration failures: log warning, continue without those args
- Plugin early handler failures: log error, continue normal startup
- Conflicting arg names: first plugin wins, log warning for duplicates

## Testing

```python
def test_plugin_registers_args():
    parser = argparse.ArgumentParser()
    AgentOrchestratorPlugin.register_cli_args(parser)

    args = parser.parse_args(["--session", "test", "--capture", "100"])
    assert args.session == "test"
    assert args.capture == 100

def test_early_exit_handler():
    args = argparse.Namespace(session="test", capture=100)

    # Should return True (exit requested)
    with pytest.raises(SystemExit):
        AgentOrchestratorPlugin.handle_early_args(args)

def test_no_early_exit_without_capture():
    args = argparse.Namespace(session="test", capture=None)

    # Should return False (continue normal startup)
    assert AgentOrchestratorPlugin.handle_early_args(args) == False
```

## Implementation Order

1. Add `register_cli_args()` and `handle_early_args()` to BasePlugin
2. Add `discover_classes_only()` to PluginDiscovery
3. Update `cli.py` to use new flow
4. Update `TerminalLLMChat` to accept and pass args
5. Update existing plugins to receive args in `initialize()`
6. Add AgentOrchestratorPlugin with --session/--capture
