"""CLI entry point for searching text in a GitHub repository.""" import logging from typing import Annotated, List, Optional import requests import typer from config import GitHubConfig from github_client.github_client import GitHubClient LOGGER = logging.getLogger('github_client.cli') app: typer.Typer = typer.Typer(help='GitHub CLI tool.') class _State: """Module-level state shared between callback and subcommands.""" repo: str = 'theorchard/terraform-infra' base_branch: str = 'master' state = _State() @app.callback() def callback( repo: Annotated[ str, typer.Option(help="GitHub repository in the format 'owner/repo'."), ] = 'theorchard/terraform-infra', base_branch: Annotated[ str, typer.Option(help='Base branch for the repository.'), ] = 'master', debug: Annotated[ bool, typer.Option('--debug/--no-debug', help='Show debug logs.'), ] = False, ) -> None: """Configure shared options for the GitHub CLI.""" level = logging.DEBUG if debug else logging.INFO logging.basicConfig( level=level, format='%(asctime)s %(name)s %(levelname)s: %(message)s' ) state.repo = repo state.base_branch = base_branch @app.command() def search_text_in_org( org: Annotated[str, typer.Option(help='GitHub organization name.')], search_text: Annotated[ str, typer.Option(help='Text to search for in the organization.') ], repo_filter: Annotated[ Optional[str], typer.Option(help='Optional repository name filter.'), ] = None, ) -> None: """Search for text across all repositories in a GitHub organization.""" try: cfg = GitHubConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) client = GitHubClient(cfg) try: results = client.search_text_occurrences_in_org(org, search_text, repo_filter) except Exception as exc: LOGGER.error( "Failed to search for '%s' in organization '%s': %s", search_text, org, exc, ) raise typer.Exit(code=1) if not results or len(results) == 0: LOGGER.info(f"No occurrences of '{search_text}' found in organization {org}.") else: for item in results: file_path = item.get('path') html_url = item.get('html_url') repo_name = item.get('repository', {}).get('full_name', 'unknown') LOGGER.info(f'Found in {repo_name}: {file_path} - {html_url}') @app.command() def offboard_user_with_copilot( issue_description: Annotated[str, typer.Option(help='Description for the issue.')], user_email: Annotated[ Optional[str], typer.Option(help='E-mail address of the offboarded user.'), ] = None, user_full_name: Annotated[ Optional[str], typer.Option(help='Full name of the offboarded user.'), ] = None, ) -> None: """Create an offboarding issue in the repo.""" try: cfg = GitHubConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) client = GitHubClient(cfg) repo = state.repo title_parts = ['Offboard user'] if user_full_name: title_parts.append(f'{user_full_name}') if user_email: title_parts.append(f'({user_email})') issue_title = ' '.join(title_parts) try: issue = client.create_issue(repo, issue_title, issue_description) LOGGER.info(f'Issue created: {issue.get("html_url")}') except requests.exceptions.HTTPError as exc: LOGGER.error('GitHub API error %s', exc) if exc.response is not None: LOGGER.error('Response body: %s', exc.response.text) raise typer.Exit(code=1) except Exception as exc: LOGGER.error( "Failed to create issue in repository '%s': %s", repo, exc, ) raise typer.Exit(code=1) @app.command() def create_issue( title: Annotated[str, typer.Option(help='Issue title.')], body: Annotated[Optional[str], typer.Option(help='Issue body.')] = None, assignees: Annotated[ Optional[List[str]], typer.Option(help='Assignee username(s). Repeat for multiple.'), ] = None, ) -> None: """Create an issue directly — useful for debugging API errors.""" try: cfg = GitHubConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) client = GitHubClient(cfg) repo = state.repo try: issue = client.create_issue(repo, title, body, assignees or []) LOGGER.info( 'Issue created: %s (number: %d)', issue.get('html_url'), issue.get('number', 0), ) except requests.exceptions.HTTPError as exc: LOGGER.error('GitHub API error %s', exc) if exc.response is not None: LOGGER.error('Response body: %s', exc.response.text) raise typer.Exit(code=1) except Exception as exc: LOGGER.error('Unexpected error: %s', exc) raise typer.Exit(code=1) if __name__ == '__main__': app()