# Task Recon: Create main Typer app with global options callback

**Task ID:** 11.1
**Status:** pending
**Generated:** 2026-01-28T23:30:00Z
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Implement the main Typer application in `src/teams_phone/cli/main.py` with a callback function that defines all global CLI options (`--json`, `--verbose`, `--tenant`, `--no-color`) and stores them in the Typer context object for downstream commands.

### Parent Task Context

**Parent Task:** Implement Root CLI App with Global Options (ID: 11)
**Parent Goal:** Create the main Typer CLI application with global options and command group registration (users, numbers, policies, locations, tenants, auth).
**This Subtask's Role:** Subtask 11.1 creates the foundation - the main Typer app with global options callback. Subsequent subtasks (11.2, 11.3) will add the entry point, command group registration, and typed context with dependency injection.

### Dependencies
- Depends on: Task 4 (ConfigManager - DONE), Task 5 (CacheManager - DONE), Task 6 (AuthService - DONE), Task 8 (OutputFormatter - DONE)

### Acceptance Criteria
- Create `src/teams_phone/cli/main.py` with main Typer app
- Instantiate `app = typer.Typer()` with name='teams-phone', help text, and no_args_is_help=True
- Define `@app.callback()` decorated function accepting:
  - `ctx: typer.Context`
  - `json_output: bool` with `Option(False, '--json')`
  - `verbose: bool` with `Option(False, '--verbose', '-v')`
  - `tenant: Optional[str]` with `Option(None, '--tenant', '-t')`
  - `no_color: bool` with `Option(False, '--no-color')`
- Use `ctx.ensure_object(dict)` and populate `ctx.obj` with all option values
- Create `src/teams_phone/cli/__init__.py` if it doesn't exist (it exists, just a docstring)

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 4 | ConfigManager | Complete - uses TOML for config, raises `ConfigurationError`/`NotFoundError` for issues |
| 5 | CacheManager | Complete - token storage, CSV reading with graceful degradation |
| 6 | AuthService | Complete - MSAL auth, token caching, `_resolve_tenant()` pattern for tenant name handling |
| 8 | OutputFormatter | Complete - Rich tables, JSON mode, `no_color` respects both flag AND `NO_COLOR` env var |

### Reusable Patterns from Dependencies
- **NO_COLOR handling in OutputFormatter**: `self.no_color = no_color or os.environ.get("NO_COLOR") is not None` - see `output_formatter.py:51`
- **TenantService.switch_tenant auto-login**: Pattern for handling `--tenant` override - see `task_010.md` notes

### Warnings and Gotchas
- **OutputFormatter requires json_mode and no_color at init time**: The callback must capture these values early enough to pass to OutputFormatter when it's initialized (subtask 11.3 handles DI)
- **Typer type ignore**: May need `# type: ignore[import-untyped]` for typer if mypy complains (though typer has py.typed marker in newer versions)

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

---

## Architecture Context

### Relevant Design Decisions
- **Layered architecture**: CLI layer calls services; services call infrastructure
- **Global options via Typer callback**: Standard pattern per docs
- **Context object for state propagation**: Typer uses Click's Context under the hood; `ctx.obj` holds shared state

### Module Structure
```
src/teams_phone/
├── cli/
│   ├── __init__.py      # Exists - just docstring, will export app
│   └── main.py          # TO CREATE - main Typer app with callback
├── services/            # Already implemented
├── infrastructure/      # Already implemented (ConfigManager, OutputFormatter, etc.)
```

### Conventions to Follow
- Use `from __future__ import annotations` for forward references
- Type hints on all public functions (mypy strict mode)
- Docstrings following Google style
- Exception hierarchy from `teams_phone.exceptions`

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/infrastructure/output_formatter.py` | 35-59 | How OutputFormatter handles json_mode and no_color |
| `src/teams_phone/cli/__init__.py` | 1 | Existing CLI layer init (just docstring) |
| `.taskmaster/docs/cli-specification/global-options.md` | 1-15 | Specification for global options |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/services/auth_service.py` | 50-65 | Example of using Optional[str] for tenant_name |
| `src/teams_phone/infrastructure/config_manager.py` | 30-38 | ConfigManager init pattern |
| `src/teams_phone/constants.py` | 55-97 | ExitCode enum for error handling |

### Code Patterns to Follow

```python
# From output_formatter.py:35-51 - handling json_mode and no_color
class OutputFormatter:
    def __init__(
        self,
        console: Console | None = None,
        *,
        json_mode: bool = False,
        no_color: bool = False,
    ) -> None:
        self.json_mode = json_mode
        # Respect both the flag and the NO_COLOR environment variable
        self.no_color = no_color or os.environ.get("NO_COLOR") is not None
```

---

## Dependencies

### Internal Modules
- `typer` - CLI framework (already in pyproject.toml)
- `teams_phone.infrastructure.OutputFormatter` - for later context injection
- `teams_phone.infrastructure.ConfigManager` - for later context injection

### External Packages
- `typer>=0.9.0` - Already in pyproject.toml dependencies
- `rich>=13.0.0` - Already present (used by OutputFormatter)

### New Dependencies Needed
- None required - typer is already a dependency

---

## Testing Guidance

### Test File Location
`tests/unit/test_cli_main.py` (to create)

### Test Patterns in Use
```python
# From tests/unit/test_output_formatter.py:24-28 - unit test pattern
class TestOutputFormatterInit:
    """Tests for OutputFormatter initialization."""

    @pytest.mark.unit
    def test_default_initialization(self) -> None:
        """OutputFormatter creates Console with default settings."""
        formatter = OutputFormatter()
        assert formatter.json_mode is False
        assert formatter.console is not None
```

### Fixtures Available
- `tmp_path` - pytest built-in for temporary directories
- `monkeypatch` - pytest built-in for env var manipulation

### Test Utilities
- Typer's `CliRunner` from `typer.testing` for CLI testing
- `from typer.testing import CliRunner` then `runner = CliRunner()` and `result = runner.invoke(app, ["--help"])`

---

## Risks and Blockers

### Technical Risks
- [ ] **Context type safety**: Using raw `dict` for `ctx.obj` loses type information - subtask 11.3 addresses this with CLIContext dataclass

### Dependency Risks
- None - all dependency tasks (4, 5, 6, 8) are complete

### Open Questions
- [ ] Should `--no-color` also check the `NO_COLOR` env var at callback time, or defer to OutputFormatter? (OutputFormatter already handles it, so just pass the flag through)

---

## Implementation Recommendations

### Suggested Approach
1. Create `src/teams_phone/cli/main.py` with minimal imports
2. Define `app = typer.Typer(name="teams-phone", help="...", no_args_is_help=True)`
3. Add `@app.callback()` with all global options
4. Use `ctx.ensure_object(dict)` pattern per Typer docs
5. Store options in `ctx.obj["json"]`, `ctx.obj["verbose"]`, `ctx.obj["tenant"]`, `ctx.obj["no_color"]`
6. Update `src/teams_phone/cli/__init__.py` to export `app`
7. Write unit tests using CliRunner

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Create | `src/teams_phone/cli/main.py` | Main Typer app with callback |
| Modify | `src/teams_phone/cli/__init__.py` | Export `app` from main |
| Create | `tests/unit/test_cli_main.py` | Unit tests for CLI app |

### Quick Wins
- Start with the app callback only (no commands yet) and verify `--help` works
- Test that options are captured in context before adding any commands

---

## Cheat Sheet

### Key Imports
```python
from typing import Optional
import typer

# Will be used in subtask 11.3 for context injection
from teams_phone.infrastructure import ConfigManager, OutputFormatter
```

### Key Functions to Use
- `typer.Typer(name=..., help=..., no_args_is_help=True)` - create the app
- `@app.callback()` - decorator for global options function
- `typer.Option(default, "--flag", "-short", help="...")` - define option
- `ctx.ensure_object(dict)` - ensure ctx.obj is a dict
- `typer.testing.CliRunner` - for unit testing

### Common Patterns
- **Global options callback**: See Typer docs pattern above
- **Context object storage**: `ctx.obj["key"] = value`
- **Option definition**: `flag: bool = typer.Option(False, "--flag", help="Help text")`

### Typer Callback Example (from Context7 docs)
```python
import typer
from typing import Optional

app = typer.Typer()

@app.callback()
def main(
    ctx: typer.Context,
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
):
    ctx.ensure_object(dict)
    ctx.obj["verbose"] = verbose
```

---

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