# Task ID: 14

**Title:** Implement Tenants CLI Commands

**Status:** pending

**Dependencies:** 8, 11, 13

**Priority:** high

**Description:** Create the tenants command group with list, add, remove, switch, current, and test commands.

**Details:**

Create `src/teams_phone/cli/tenants.py`:
```python
tenants_app = typer.Typer(name="tenants", help="Tenant profile management")

@tenants_app.command("list")
def list_tenants(ctx: typer.Context): ...

@tenants_app.command()
def add(
    ctx: typer.Context,
    name: str,
    tenant_id: str,
    client_id: str,
    display_name: str = typer.Option(...),
    auth_method: AuthMethod = typer.Option(AuthMethod.CERTIFICATE),
    cert_path: Optional[str] = typer.Option(None),
    default_location: Optional[str] = typer.Option(None),
): ...

@tenants_app.command()
def remove(ctx: typer.Context, name: str, force: bool = typer.Option(False)): ...

@tenants_app.command()
def switch(ctx: typer.Context, name: str): ...

@tenants_app.command()
def current(ctx: typer.Context): ...

@tenants_app.command()
def test(ctx: typer.Context, name: Optional[str] = typer.Argument(None)): ...
```

**Test Strategy:**

CLI tests: verify add creates config, list shows tenants in table/JSON, switch updates default, remove with confirmation, test reports connection status.

---

## Implementation Notes

### Session: 2026-01-29 16:30 - Agent: claude-sonnet-4-5-20250929

**Status:** Implemented

#### What Was Done
- Created `src/teams_phone/cli/tenants.py` with all 6 CLI commands:
  - `list` - Shows all configured tenants in table/JSON format with current tenant indicator
  - `current` - Displays the currently active tenant profile
  - `switch` - Changes the active tenant (with `--no-login` option to skip auto-login)
  - `test` - Tests connection to a tenant (defaults to current tenant if no name provided)
  - `remove` - Deletes a tenant profile with confirmation prompt (skipable with `--force`)
  - `add` - Creates a new tenant profile with UUID validation for tenant_id and client_id
- Modified `src/teams_phone/cli/main.py`:
  - Added import for `tenants_app` from `tenants.py`
  - Removed placeholder `tenants_app` definition
- Created `tests/unit/test_cli_tenants.py` with 41 unit tests covering:
  - Help text visibility for all commands
  - Happy path scenarios for each command
  - Error handling (NotFoundError, ConfigurationError, ValidationError)
  - JSON output validation
  - Argument/option validation

#### Key Decisions Made
- **Non-interactive add command**: Implemented `add` as fully non-interactive with required arguments (`name`, `tenant_id`, `client_id`) and required option (`--display-name`). This follows task details over CLI spec's "interactive wizard" mention, as it's more suitable for automation and scripting.
- **UUID validation**: Added explicit UUID validation in the `add` command before creating TenantProfile, with clear error messages and exit code 5 (ValidationError).
- **Confirmation by default for remove**: Implemented confirmation prompt for `remove` command (as per CLI spec), skipable with `--force` flag.
- **Switch auto-login**: Added `--no-login` flag to `switch` command to allow skipping automatic login when switching tenants.
- **Helper function**: Created `_create_tenant_service()` helper to reduce code duplication across commands.

#### Patterns Discovered
- **Typer error messages**: Typer outputs missing argument/option errors to stderr, not stdout. Tests needed to handle this with `result.stderr or ""`.
- **Type annotations**: Use `TYPE_CHECKING` import guard for type hints to avoid circular imports (see `tenants.py:7-11`).

#### Open Items for Next Agent
- None - all acceptance criteria met

#### Gotchas and Warnings
- **Exit code 2**: When Typer commands are invoked without required arguments, they return exit code 2 (not 1).
- **Test confirmation prompts**: Use `runner.invoke(app, [...], input="y\n")` to simulate user confirmation in tests.
- **JSON mode test failure**: When `test_connection()` returns `success=False`, the CLI still outputs JSON (exit code 0) in JSON mode, but exits with code 1 in normal mode.

---
