---
phase: 04-installation-fleet-status
plan: 02
type: execute
wave: 2
depends_on: [04-01]
files_modified:
  - src/clawrium/cli/install.py
  - src/clawrium/cli/main.py
  - tests/test_cli_install.py
autonomous: true
requirements: [INST-01, INST-03]

must_haves:
  truths:
    - "User can run clm install and complete installation via prompts"
    - "User can run clm install --claw openclaw --host kevin to skip prompts"
    - "User sees step-by-step progress with spinners during installation"
    - "User sees confirmation prompt with summary before installation starts"
  artifacts:
    - path: "src/clawrium/cli/install.py"
      provides: "Interactive install command with progress display"
      exports: ["install"]
    - path: "src/clawrium/cli/main.py"
      provides: "Main CLI with install command registered"
      contains: "import install"
    - path: "tests/test_cli_install.py"
      provides: "CLI install command tests"
      min_lines: 80
  key_links:
    - from: "src/clawrium/cli/install.py"
      to: "src/clawrium/core/install.py"
      via: "run_installation import"
      pattern: "from clawrium.core.install import run_installation"
    - from: "src/clawrium/cli/main.py"
      to: "src/clawrium/cli/install.py"
      via: "command registration"
      pattern: "@app.command"
---

<objective>
Create the `clm install` CLI command with interactive prompts, flag overrides, confirmation dialog, and Rich spinner progress display during installation.

Purpose: User-facing installation flow that wraps core install module with polished UX.
Output: Working `clm install` command that guides users through installation or runs directly with flags.
</objective>

<execution_context>
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/workflows/execute-plan.md
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/04-installation-fleet-status/04-CONTEXT.md

<interfaces>
<!-- From Plan 01 - core/install.py -->
```python
class InstallationError(Exception):
    """Raised when installation fails."""
    pass

class InstallResult(TypedDict):
    success: bool
    claw: str
    version: str
    host: str
    playbooks_run: list[str]
    error: str | None

def run_installation(
    claw_name: str,
    hostname: str,
    on_event: Callable[[str, str], None] | None = None,
) -> InstallResult
```

<!-- From existing CLI patterns -->
From src/clawrium/cli/host.py:
```python
console = Console()
host_app = typer.Typer(name="host", help="...", no_args_is_help=True)

@host_app.command()
def add(
    hostname: str = typer.Argument(..., help="..."),
    port: Optional[int] = typer.Option(None, "--port", "-p", help="..."),
) -> None:
```

From src/clawrium/core/registry.py:
```python
def list_claws() -> list[str]
def get_claw_info(claw_name: str) -> dict  # returns {name, description, latest_version, supported_platforms}
def check_compatibility(claw_name: str, hardware: dict, version: str | None = None) -> CompatibilityResult
```

From src/clawrium/core/hosts.py:
```python
def load_hosts() -> list[dict]
def get_host(identifier: str) -> dict | None
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Create install CLI command with interactive flow and progress display</name>
  <files>src/clawrium/cli/install.py, src/clawrium/cli/main.py, tests/test_cli_install.py</files>
  <read_first>
    - src/clawrium/cli/host.py (CLI patterns, Rich console usage, Typer options)
    - src/clawrium/cli/registry.py (list_claws, get_claw_info usage)
    - src/clawrium/cli/main.py (how to register new commands)
    - src/clawrium/core/install.py (run_installation signature from Plan 01)
    - tests/test_cli_host.py (CLI testing patterns with CliRunner)
  </read_first>
  <behavior>
    - Test: clm install with no args prompts for claw selection from list_claws()
    - Test: clm install with no host prompts for host selection from load_hosts()
    - Test: clm install --claw openclaw --host kevin skips prompts
    - Test: clm install shows confirmation with claw name, version, host before proceeding
    - Test: clm install shows Rich spinner during installation phases
    - Test: clm install exits 1 on InstallationError with error message
  </behavior>
  <action>
Create `src/clawrium/cli/install.py`:

```python
"""Install command for deploying claws to hosts."""

from typing import Optional

import typer
from rich.console import Console
from rich.markup import escape
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn

from clawrium.core.hosts import load_hosts, get_host, HostsFileCorruptedError
from clawrium.core.install import run_installation, InstallationError
from clawrium.core.registry import (
    list_claws,
    get_claw_info,
    check_compatibility,
    ManifestNotFoundError,
)

__all__ = ["install"]

console = Console()


def _select_claw() -> str:
    """Prompt user to select a claw from registry."""
    claws = list_claws()
    if not claws:
        console.print("[red]Error:[/red] No claws available in registry")
        raise typer.Exit(code=1)

    console.print("\n[bold]Available claws:[/bold]")
    for i, claw in enumerate(claws, 1):
        try:
            info = get_claw_info(claw)
            console.print(f"  {i}. {escape(claw)} (v{info['latest_version']}) - {escape(info['description'])}")
        except ManifestNotFoundError:
            console.print(f"  {i}. {escape(claw)} (manifest error)")

    console.print()
    choice = typer.prompt("Select claw", type=int)
    if choice < 1 or choice > len(claws):
        console.print("[red]Invalid selection[/red]")
        raise typer.Exit(code=1)

    return claws[choice - 1]


def _select_host() -> str:
    """Prompt user to select a host from registered hosts."""
    try:
        hosts = load_hosts()
    except HostsFileCorruptedError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(code=1)

    if not hosts:
        console.print("[red]Error:[/red] No hosts registered. Run 'clm host add' first.")
        raise typer.Exit(code=1)

    console.print("\n[bold]Available hosts:[/bold]")
    for i, host in enumerate(hosts, 1):
        name = host.get("alias") or host["hostname"]
        hw = host.get("hardware", {})
        arch = hw.get("architecture", "?")
        mem_gb = round(hw.get("memtotal_mb", 0) / 1024, 1) if hw.get("memtotal_mb") else "?"
        console.print(f"  {i}. {escape(name)} ({arch}, {mem_gb}GB)")

    console.print()
    choice = typer.prompt("Select host", type=int)
    if choice < 1 or choice > len(hosts):
        console.print("[red]Invalid selection[/red]")
        raise typer.Exit(code=1)

    # Return hostname for lookup
    selected = hosts[choice - 1]
    return selected.get("alias") or selected["hostname"]


def install(
    claw: Optional[str] = typer.Option(
        None, "--claw", "-c", help="Claw type to install (e.g., openclaw)"
    ),
    host: Optional[str] = typer.Option(
        None, "--host", "-H", help="Target host (hostname or alias)"
    ),
    yes: bool = typer.Option(
        False, "--yes", "-y", help="Skip confirmation prompt"
    ),
) -> None:
    """Install a claw on a host.

    Without flags, prompts for claw and host selection interactively.
    With --claw and --host flags, runs directly (per D-01 hybrid invocation).
    """
    # Step 1: Get claw (prompt if not provided per D-01)
    selected_claw = claw or _select_claw()

    # Step 2: Validate claw exists
    try:
        claw_info = get_claw_info(selected_claw)
    except ManifestNotFoundError:
        console.print(f"[red]Error:[/red] Claw '{escape(selected_claw)}' not found in registry")
        raise typer.Exit(code=1)

    # Step 3: Get host (prompt if not provided per D-01)
    selected_host = host or _select_host()

    # Step 4: Load host and check compatibility
    host_record = get_host(selected_host)
    if not host_record:
        console.print(f"[red]Error:[/red] Host '{escape(selected_host)}' not found")
        raise typer.Exit(code=1)

    hardware = host_record.get("hardware", {})
    compat = check_compatibility(selected_claw, hardware)

    if not compat["compatible"]:
        console.print(f"[red]Error:[/red] Host is incompatible with {selected_claw}:")
        for reason in compat["reasons"]:
            console.print(f"  - {reason}")
        raise typer.Exit(code=1)

    matched_version = compat["matched_entry"]["version"]
    display_host = host_record.get("alias") or host_record["hostname"]

    # Step 5: Show confirmation summary (per D-03)
    summary = Panel(
        f"[bold]Claw:[/bold] {selected_claw}\n"
        f"[bold]Version:[/bold] {matched_version}\n"
        f"[bold]Host:[/bold] {display_host}\n"
        f"[bold]Architecture:[/bold] {hardware.get('architecture', 'unknown')}\n"
        f"[bold]Memory:[/bold] {round(hardware.get('memtotal_mb', 0) / 1024, 1)}GB",
        title="Installation Summary",
        border_style="cyan",
    )
    console.print(summary)

    if not yes and not typer.confirm("\nProceed with installation?", default=False):
        console.print("Installation cancelled.")
        raise typer.Exit(code=0)

    # Step 6: Run installation with progress spinner (per D-02)
    current_stage = [""]  # Mutable to update from callback

    def on_event(stage: str, message: str) -> None:
        current_stage[0] = f"[{stage}] {message}"

    console.print()  # Blank line before progress

    try:
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
            transient=True,
        ) as progress:
            task = progress.add_task("Starting installation...", total=None)

            def update_progress(stage: str, message: str) -> None:
                progress.update(task, description=f"[{stage}] {message}")

            result = run_installation(
                claw_name=selected_claw,
                hostname=selected_host,
                on_event=update_progress,
            )

        # Success
        console.print(f"[green]Success![/green] {selected_claw} v{result['version']} installed on {display_host}")

    except InstallationError as e:
        # Error display per D-10
        console.print(f"[red]Installation failed:[/red] {e}")
        raise typer.Exit(code=1)
```

Update `src/clawrium/cli/main.py` to register the install command:

```python
# Add at imports:
from clawrium.cli.install import install as install_command

# Add after registry_app registration:
@app.command()
def install(
    claw: Optional[str] = typer.Option(None, "--claw", "-c", help="Claw type to install"),
    host: Optional[str] = typer.Option(None, "--host", "-H", help="Target host"),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
) -> None:
    """Install a claw on a host."""
    install_command(claw=claw, host=host, yes=yes)
```

Create `tests/test_cli_install.py` with tests:
1. test_install_prompts_for_claw - no --claw flag triggers prompt
2. test_install_prompts_for_host - no --host flag triggers prompt
3. test_install_with_flags_skips_prompts - --claw and --host go direct
4. test_install_shows_confirmation - output contains "Installation Summary"
5. test_install_yes_skips_confirmation - --yes proceeds without confirm
6. test_install_cancelled_exits_0 - declining confirm exits cleanly
7. test_install_error_exits_1 - InstallationError shows error, exits 1
8. test_install_incompatible_exits_1 - incompatible host shows reasons, exits 1
  </action>
  <verify>
    <automated>cd /home/devashish/workspace/ric03uec/clawrium && python -m pytest tests/test_cli_install.py -v --tb=short 2>&1 | tail -40</automated>
  </verify>
  <acceptance_criteria>
    - src/clawrium/cli/install.py exists
    - install.py contains "def install" function
    - install.py contains "def _select_claw" for interactive selection
    - install.py contains "def _select_host" for interactive selection
    - install.py contains "from clawrium.core.install import run_installation"
    - install.py contains "Progress" from rich.progress for spinner
    - install.py contains "Panel" for confirmation summary
    - src/clawrium/cli/main.py contains "from clawrium.cli.install import"
    - main.py contains "@app.command" decorator for install
    - tests/test_cli_install.py exists with at least 6 test functions
    - All tests in test_cli_install.py pass
    - clm install --help shows --claw, --host, --yes options
  </acceptance_criteria>
  <done>clm install command with interactive prompts, progress display, and confirmation</done>
</task>

</tasks>

<verification>
- clm install --help shows all options
- clm install (no args) prompts for claw and host selection
- clm install --claw openclaw --host myhost shows confirmation and proceeds
- clm install --claw openclaw --host myhost --yes skips confirmation
- Progress spinner visible during installation
- Error messages clear and actionable
- All CLI tests pass
</verification>

<success_criteria>
- Users can complete installation interactively or via flags (D-01)
- Step-by-step progress shown during install (D-02)
- Confirmation with summary displayed before install (D-03)
- Tests cover success, error, and cancellation paths
</success_criteria>

<output>
After completion, create `.planning/phases/04-installation-fleet-status/04-02-SUMMARY.md`
</output>
