# Task ID: 11

**Title:** Implement Root CLI App with Global Options

**Status:** pending

**Dependencies:** 4, 5, 6, 8

**Priority:** high

**Description:** Create the main Typer CLI application with global options (--json, --verbose, --tenant, --no-color) and command group registration.

**Details:**

Create `src/teams_phone/cli/main.py`:
```python
import typer
from typing import Optional

app = typer.Typer(
    name="teams-phone",
    help="Manage Microsoft Teams Phone configurations from the command line.",
    no_args_is_help=True,
)

@app.callback()
def main(
    ctx: typer.Context,
    json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
    tenant: Optional[str] = typer.Option(None, "--tenant", "-t", help="Override active tenant"),
    no_color: bool = typer.Option(False, "--no-color", help="Disable colored output"),
):
    ctx.ensure_object(dict)
    ctx.obj["json"] = json_output
    ctx.obj["verbose"] = verbose
    ctx.obj["tenant"] = tenant
    ctx.obj["no_color"] = no_color
```

Create `src/teams_phone/__main__.py`:
```python
from teams_phone.cli.main import app
app()
```

Register command groups: users, numbers, policies, locations, tenants, auth.

**Test Strategy:**

Test CLI invocation with `--help`, verify global options are passed to context, test version display, test no-args shows help.

---

## Implementation Notes

### Session: 2026-01-28 23:45 - Agent: claude-sonnet-4-5-20250929

**Subtask:** 11.1 - Create main Typer app with global options callback

**Status:** Implemented

#### What Was Done
- Created `src/teams_phone/cli/main.py` with Typer app and callback
- Instantiated `app = typer.Typer()` with name='teams-phone', help text, and no_args_is_help=True
- Defined `@app.callback()` with all global options: `--json/-j`, `--verbose/-v`, `--tenant/-t`, `--no-color`
- Used `ctx.ensure_object(dict)` and populated `ctx.obj` with all option values
- Updated `src/teams_phone/cli/__init__.py` to export `app` from the package
- Created comprehensive unit tests in `tests/unit/test_cli_main.py` (15 tests)

#### Key Decisions Made
- **Context key naming**: Used `ctx.obj["json"]` (not `ctx.obj["json_output"]`) to keep keys concise and consistent with option semantics
- **Help text style**: Used imperative sentences ending with period ("Output in JSON format for automation.") matching project's existing style in OutputFormatter
- **no_args_is_help behavior**: Typer returns exit code 2 (not 0) when no_args_is_help triggers help display - tests account for this Click behavior
- **Test approach for context verification**: Created a test fixture with temporary subcommand to capture and verify context values, since callback alone without subcommand triggers no_args_is_help exit

#### Patterns Discovered
- Typer CliRunner from `typer.testing` is the standard for unit testing Typer apps
- `no_args_is_help=True` uses Click's behavior which returns exit code 2, not 0
- To test context population, register a test subcommand that captures ctx.obj values

#### Files Created/Modified
- Created: `src/teams_phone/cli/main.py` - Main Typer app with global options callback
- Modified: `src/teams_phone/cli/__init__.py` - Added export for `app`
- Created: `tests/unit/test_cli_main.py` - 15 unit tests for CLI initialization

#### Open Items for Next Agent
- [ ] Subtask 11.2: Create `__main__.py` entry point with `app()` invocation
- [ ] Subtask 11.3: Create typed CLIContext dataclass for dependency injection (replaces raw dict)
- [ ] Command group registration (users, numbers, policies, locations, tenants, auth)

#### Gotchas and Warnings
- **Exit code 2 for no_args_is_help**: When testing with no arguments or just options (no subcommand), Typer returns exit code 2 not 0 - this is expected behavior
- **Short option -j for --json**: Added `-j` short form which wasn't in original task spec but matches cli-specification/global-options.md

---

### Session: 2026-01-28 12:10 - Agent: claude-sonnet-4-5-20250929

**Subtask:** 11.2 - Set up __main__.py entry point and command group registration

**Status:** Implemented

#### What Was Done
- Completed `src/teams_phone/__main__.py` with proper entry point (`from teams_phone.cli.main import app` + `if __name__ == "__main__": app()`)
- Created 6 placeholder sub-apps in `src/teams_phone/cli/main.py`: users_app, numbers_app, policies_app, locations_app, tenants_app, auth_app
- Registered all 6 command groups via `app.add_typer()` with name and help text
- Added 9 new unit tests in `tests/unit/test_cli_main.py` covering:
  - `test_command_groups_in_help` - verifies all 6 groups in help output
  - Individual help tests for each group (users, numbers, policies, locations, tenants, auth)
  - `test_command_group_no_args_shows_help` - verifies exit code 2 behavior for all groups
- Verified `python -m teams_phone --help` shows all command groups correctly

#### Key Decisions Made
- **Sub-app placement**: Created sub-apps in `main.py` rather than separate module files, since they're placeholders until actual commands are implemented
- **no_args_is_help=True for all sub-apps**: Consistent with main app behavior, empty command groups show help
- **Help text matching CLI specs**: Used exact help text from `.taskmaster/docs/cli-specification/command-summary.md` (e.g., "Manage Teams users." not "User management commands")

#### Files Modified
- Modified: `src/teams_phone/__main__.py` - Added app import and entry point
- Modified: `src/teams_phone/cli/main.py` - Added 6 placeholder sub-apps with `add_typer()` registration
- Modified: `tests/unit/test_cli_main.py` - Added TestCommandGroupRegistration class with 9 tests

#### Patterns Discovered
- `app.add_typer(sub_app, name="...", help="...")` - Standard Typer pattern for command groups
- Sub-apps with `no_args_is_help=True` return exit code 2 when invoked without subcommand (consistent with Click behavior)

#### Open Items for Next Agent
- [ ] Subtask 11.3: Create typed CLIContext dataclass for dependency injection (replaces raw dict)
- [ ] Future tasks will add actual commands to each command group

#### Gotchas and Warnings
- **Sub-apps are placeholders**: All 6 sub-apps are empty Typer() instances until actual commands are implemented in later tasks
- **Help text specified in both places**: The `help` parameter is specified both in `typer.Typer()` constructor and in `app.add_typer()` - the one in `add_typer()` takes precedence for display

---

### Session: 2026-01-29 12:15 - Agent: claude-sonnet-4-5-20250929

**Subtask:** 11.3 - Implement context object propagation and dependency injection setup

**Status:** Implemented

#### What Was Done
- Created `src/teams_phone/cli/context.py` with:
  - `CLIContext` dataclass containing all global options (`json_output`, `verbose`, `tenant`, `no_color`) and service references (`config_manager`, `output_formatter`)
  - `get_output_formatter()` method for lazy OutputFormatter creation
  - `get_config_manager()` method for lazy ConfigManager creation
  - `get_context(ctx: typer.Context) -> CLIContext` helper for type-safe context retrieval
- Updated `src/teams_phone/cli/main.py` callback to:
  - Import and use `CLIContext` instead of raw dict
  - Create `CLIContext` instance with global options
  - Initialize `OutputFormatter` immediately (cheap operation)
  - Leave `ConfigManager` as `None` for lazy initialization (avoids disk I/O on `--help`)
- Updated `src/teams_phone/cli/__init__.py` to export `CLIContext` and `get_context`
- Added 25 new unit tests in `tests/unit/test_cli_main.py`:
  - `TestCLIContext` class (8 tests): Default values, custom values, lazy service creation, singleton behavior
  - `TestGetContext` class (3 tests): Type-safe retrieval, RuntimeError on missing context
  - `TestContextServiceInitialization` class (4 tests): OutputFormatter initialization, json/no_color flags, lazy ConfigManager
  - Updated `TestCLIExport` with 2 tests for new exports

#### Key Decisions Made
- **Dataclass over TypedDict**: Used `@dataclass` for better IDE support, default values, and methods for lazy initialization
- **Lazy ConfigManager, eager OutputFormatter**: ConfigManager deferred to avoid disk I/O on `--help`; OutputFormatter initialized immediately since it's cheap and always needed
- **RuntimeError for missing context**: `get_context()` raises `RuntimeError` (not custom exception) since missing context indicates a programming error
- **Import inside methods**: Used deferred imports in `get_output_formatter()` and `get_config_manager()` to avoid circular imports and keep TYPE_CHECKING imports clean

#### Files Created/Modified
- Created: `src/teams_phone/cli/context.py` - CLIContext dataclass and get_context helper
- Modified: `src/teams_phone/cli/main.py` - Use CLIContext instead of dict
- Modified: `src/teams_phone/cli/__init__.py` - Export CLIContext and get_context
- Modified: `tests/unit/test_cli_main.py` - Added 25 new tests (40 total)

#### Patterns Discovered
- **Lazy initialization pattern**: Store `None`, check on access, create if needed, return same instance - see `get_output_formatter()` and `get_config_manager()`
- **TYPE_CHECKING imports**: Use `if TYPE_CHECKING:` for imports only needed for type hints to avoid runtime circular imports
- **Fixture for context testing**: Create test app with callback and capture command to inspect CLIContext values

#### Gotchas and Warnings
- **Existing tests updated**: Tests in `TestContextPopulation` now use `get_context()` and access `cli_ctx.json_output` instead of `ctx.obj["json"]`
- **OutputFormatter initialized in callback**: Even though CLIContext has `get_output_formatter()` for lazy init, the callback pre-initializes it for immediate use
- **Context field naming**: `CLIContext.json_output` matches Option name, while old dict key was `ctx.obj["json"]` - keep both usages in mind when migrating commands

---
