# Task Recon: Implement Auth CLI Commands

**Task ID:** 12
**Status:** pending
**Generated:** 2026-01-29T15:50:00Z
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Create the auth command group with login, logout, status, and refresh commands. This task involves implementing CLI commands that expose the AuthService functionality to users, handling global options from CLIContext, and providing both table and JSON output formats.

### Dependencies
- Depends on: 6 (AuthService with MSAL), 8 (OutputFormatter), 11 (Root CLI App with Global Options)

### Acceptance Criteria
- Create `src/teams_phone/cli/auth.py` with four commands: `login`, `logout`, `status`, `refresh`
- Wire commands to AuthService methods
- Support both table and JSON output formats via `--json` global option
- Handle errors with appropriate exit codes and remediation messages
- Respect `--tenant` override from global options

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 6 | AuthService with MSAL | AuthService is fully implemented with `login()`, `logout()`, `get_status()`, `refresh_token()` methods. Returns `CachedToken` (not AuthToken) from login/refresh. `is_authenticated()` never throws. |
| 8 | OutputFormatter | Use `formatter.table()` for rich output, `formatter.json()` for JSON mode. Success/error/info methods respect json_mode. |
| 11 | Root CLI App with Global Options | `CLIContext` dataclass provides `json_output`, `verbose`, `tenant`, `no_color`. Use `get_context(ctx)` for type-safe retrieval. OutputFormatter is pre-initialized in callback. |

### Reusable Patterns from Dependencies
- **CLIContext pattern**: Use `get_context(ctx)` to get typed context - see `src/teams_phone/cli/context.py:76-93`
- **Service initialization**: Services need `ConfigManager` and `CacheManager` - ConfigManager is lazily initialized via `cli_ctx.get_config_manager()`
- **Error handling**: AuthService throws `AuthenticationError`, `ConfigurationError`, `NotFoundError` - each has `exit_code` and `remediation`

### Warnings and Gotchas
- **CachedToken not AuthToken**: Task 6.3/6.4 decided to return `CachedToken` from `login()` and `refresh_token()` rather than creating a separate `AuthToken` model
- **is_authenticated() never throws**: Catches all exceptions and returns `False` - use `get_status()` if you need exception propagation
- **get_status() distinguishes EXPIRED vs UNAUTHENTICATED**: Buffer period doesn't affect status display
- **logout(all_tenants=True) clears ALL token files**: Even for unconfigured tenants
- **Exit code 2 for no_args_is_help**: Empty command groups return exit code 2, not 0

### Open Items Inherited
- None - all dependency tasks are complete

---

## Architecture Context

### Relevant Design Decisions
- **Layered Monolith**: CLI layer delegates to services, services handle business logic
- **Typed CLIContext**: Replaces raw dict for type-safe access to global options
- **OutputFormatter integration**: Pre-initialized in callback, available via `cli_ctx.output_formatter`

### Module Structure
```
src/teams_phone/
├── cli/
│   ├── __init__.py       # Exports app, CLIContext, get_context
│   ├── main.py           # Main app with auth_app placeholder
│   ├── context.py        # CLIContext dataclass and get_context helper
│   └── auth.py           # NEW - Auth commands (to be created)
├── services/
│   └── auth_service.py   # AuthService with login/logout/status/refresh
└── infrastructure/
    ├── cache_manager.py  # Token storage
    ├── config_manager.py # Tenant configuration
    └── output_formatter.py # Rich output
```

### Conventions to Follow
- Commands use `ctx: typer.Context` as first parameter to access CLIContext
- Use `get_context(ctx)` for type-safe context retrieval
- Handle exceptions with try/except, display via `formatter.error()`, and `raise typer.Exit(error.exit_code)`
- Return value serialization: Use `model_dump()` for Pydantic models in JSON output

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/cli/main.py` | 102-106 | auth_app placeholder - commands will be added here |
| `src/teams_phone/cli/context.py` | 19-93 | CLIContext dataclass and get_context helper |
| `src/teams_phone/services/auth_service.py` | 485-536 | login(), logout() methods to wrap |
| `src/teams_phone/services/auth_service.py` | 443-483 | get_status() method returning AuthStatus |
| `src/teams_phone/services/auth_service.py` | 282-333 | refresh_token() method |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/models/auth.py` | 10-79 | AuthStatusType enum and AuthStatus model |
| `src/teams_phone/infrastructure/output_formatter.py` | 61-87 | table() method for formatted output |
| `src/teams_phone/infrastructure/output_formatter.py` | 89-96 | json() method for JSON output |
| `src/teams_phone/exceptions.py` | 10-45 | TeamsPhoneError base class with exit_code and remediation |
| `.taskmaster/docs/cli-specification/command-group-5-auth.md` | 1-110 | CLI specification for auth commands |

### Code Patterns to Follow

```python
# Example CLI command pattern from task specification
# src/teams_phone/cli/auth.py (to be created)
import typer
from teams_phone.cli.context import get_context
from teams_phone.exceptions import TeamsPhoneError
from teams_phone.infrastructure import CacheManager
from teams_phone.services import AuthService

auth_app = typer.Typer(name="auth", help="Authentication commands")

@auth_app.command()
def status(ctx: typer.Context) -> None:
    """Show current authentication status."""
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()

    try:
        config_manager = cli_ctx.get_config_manager()
        cache_manager = CacheManager()
        auth_service = AuthService(config_manager, cache_manager)

        tenant_name = cli_ctx.tenant  # --tenant override
        auth_status = auth_service.get_status(tenant_name)

        if cli_ctx.json_output:
            formatter.json(auth_status.model_dump())
        else:
            # Format as table or info messages
            formatter.info(f"Status: {auth_status.status.value}")
            if auth_status.tenant_name:
                formatter.info(f"Tenant: {auth_status.tenant_name}")
            # ... more fields
    except TeamsPhoneError as e:
        formatter.error(e.message, remediation=e.remediation)
        raise typer.Exit(e.exit_code)
```

---

## Dependencies

### Internal Modules
- `teams_phone.cli.context` - CLIContext, get_context for type-safe context access
- `teams_phone.services.auth_service` - AuthService class with auth methods
- `teams_phone.infrastructure` - CacheManager, ConfigManager, OutputFormatter
- `teams_phone.models` - AuthMethod, AuthStatus, AuthStatusType, CachedToken
- `teams_phone.exceptions` - TeamsPhoneError hierarchy (AuthenticationError, ConfigurationError, NotFoundError)

### External Packages
- `typer` - CLI framework (already in use)
- `rich` - Terminal formatting (used via OutputFormatter)

### New Dependencies Needed
- None required - all dependencies already in project

---

## Testing Guidance

### Test File Location
`tests/unit/test_cli_auth.py` (to be created)

### Test Patterns in Use
```python
# Example test structure from tests/unit/test_cli_main.py:14-31
from typer.testing import CliRunner
from teams_phone.cli.main import app

runner = CliRunner()

class TestAuthStatus:
    """Tests for auth status command."""

    @pytest.mark.unit
    def test_status_unauthenticated(self, tmp_path: Path) -> None:
        """status shows unauthenticated when no token exists."""
        # Mock ConfigManager and CacheManager to avoid disk I/O
        with patch("teams_phone.cli.auth.CacheManager") as mock_cache:
            mock_cache.return_value.get_token.return_value = None
            result = runner.invoke(app, ["auth", "status"])
            assert result.exit_code == 0
            assert "unauthenticated" in result.stdout.lower()

    @pytest.mark.unit
    def test_status_json_output(self, tmp_path: Path) -> None:
        """status outputs valid JSON with --json flag."""
        # ...
```

### Fixtures Available
- `tmp_path` - pytest built-in for temp directories
- `monkeypatch` - pytest built-in for env vars and patching

### Test Utilities
- `tests/conftest.py` - Custom markers: `@pytest.mark.unit`, `@pytest.mark.integration`
- CliRunner from `typer.testing` for CLI invocation

---

## Risks and Blockers

### Technical Risks
- [ ] **Service instantiation in commands**: Need to instantiate AuthService with ConfigManager and CacheManager - ensure proper lazy initialization pattern to avoid disk I/O on `--help`

### Dependency Risks
- None - all dependencies (Tasks 6, 8, 11) are complete

### Open Questions
- [ ] **Login method option**: CLI spec mentions `--method certificate|client-secret` option, but AuthService.login() doesn't take method - it uses the profile's configured auth_method. Need to clarify if method override is needed or if we use tenant's config.
- [ ] **Login --cert-path and --cert-thumbprint**: CLI spec mentions these options but current implementation reads cert_path from tenant profile. Decide if these should override profile config or if they're only for initial setup.

---

## Implementation Recommendations

### Suggested Approach
1. **Create `src/teams_phone/cli/auth.py`** with `auth_app` Typer instance
2. **Implement `status` command first** - simplest, just reads and displays
3. **Implement `login` command** with `--force` flag
4. **Implement `logout` command** with `--all` flag
5. **Implement `refresh` command** with `--force` flag (similar to login)
6. **Update `main.py`** to import auth commands and register with existing placeholder
7. **Write unit tests** for each command with mocked services

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Create | `src/teams_phone/cli/auth.py` | Auth command implementations |
| Modify | `src/teams_phone/cli/main.py` | Replace placeholder auth_app with real implementation |
| Modify | `src/teams_phone/cli/__init__.py` | Export auth_app if needed |
| Create | `tests/unit/test_cli_auth.py` | Unit tests for auth commands |

### Quick Wins
- Start with `status` command - it's read-only and exercises the full pattern
- Copy test structure from `test_cli_main.py` for consistency

---

## Cheat Sheet

### Key Imports
```python
import typer
from teams_phone.cli.context import get_context
from teams_phone.exceptions import TeamsPhoneError
from teams_phone.infrastructure import CacheManager
from teams_phone.models import AuthStatusType
from teams_phone.services import AuthService
```

### Key Functions to Use
- `get_context(ctx)` - Get typed CLIContext from Typer context
- `cli_ctx.get_config_manager()` - Lazy ConfigManager initialization
- `cli_ctx.get_output_formatter()` - Get pre-initialized OutputFormatter
- `auth_service.login(tenant_name, force=False)` - Returns CachedToken
- `auth_service.logout(tenant_name, all_tenants=False)` - Clears tokens
- `auth_service.get_status(tenant_name)` - Returns AuthStatus
- `auth_service.refresh_token(tenant_name)` - Returns CachedToken
- `formatter.table(data, columns)` - Display table (auto-JSON in json_mode)
- `formatter.json(data)` - Output JSON
- `formatter.success(msg)` - Green success message
- `formatter.error(msg, remediation=...)` - Red error with fix guidance

### Common Patterns
- **Error handling**: See exception pattern in Code Patterns section
- **Service instantiation**: `AuthService(config_manager, CacheManager())`
- **JSON output check**: `if cli_ctx.json_output:` then use `formatter.json()`
- **Tenant override**: `tenant_name = cli_ctx.tenant` to respect `--tenant` flag

---

*Generated by task-recon (claude-opus-4-5-20251101) | Ready for implementation*
