Skip to content

cli

code_context_agent.cli

Command-line interface for code-context-agent.

This module provides the main CLI entry point using cyclopts.

Example

$ code-context-agent analyze /path/to/repo $ code-context-agent analyze /path/to/repo --focus "authentication" $ code-context-agent analyze . --quiet $ code-context-agent analyze . --issue "gh:1694"

main

main(debug=False, output_format='rich')

Run the code-context-agent CLI.

This is the main entry point for the CLI. It loads settings from the environment and displays a welcome message with current configuration.

Parameters:

Name Type Description Default
debug Annotated[bool, Parameter(help='Enable debug mode.')]

Enable debug output for verbose logging and diagnostics.

False
output_format Annotated[Literal['rich', 'json', 'plain'], Parameter(help='Output format: rich, json, plain.')]

Format for output display. Supported values are "rich" (default), "json", or "plain".

'rich'
Source code in src/code_context_agent/cli.py
@app.default
def main(
    debug: Annotated[bool, Parameter(help="Enable debug mode.")] = False,
    output_format: Annotated[
        Literal["rich", "json", "plain"],
        Parameter(help="Output format: rich, json, plain."),
    ] = "rich",
) -> None:
    """Run the code-context-agent CLI.

    This is the main entry point for the CLI. It loads settings from the
    environment and displays a welcome message with current configuration.

    Args:
        debug: Enable debug output for verbose logging and diagnostics.
        output_format: Format for output display. Supported values are
            "rich" (default), "json", or "plain".
    """
    settings = get_settings()
    if debug or output_format != "rich":
        settings = Settings(debug=debug, output_format=output_format)

    display_welcome(settings)

analyze

analyze(
    path=Path(),
    *,
    output_dir=None,
    focus="",
    issue="",
    output_format="rich",
    since="",
    full=False,
    quiet=False,
    debug=False,
)

Analyze a codebase and produce a narrated context bundle.

The agent uses LSP, AST-grep, ripgrep, repomix, and git history tools to understand the codebase, then produces a narrated markdown bundle. Analysis depth is determined automatically based on repository size and complexity.

Outputs

.code-context/CONTEXT.md - The narrated context bundle .code-context/CONTEXT.orientation.md - Token distribution tree .code-context/CONTEXT.bundle.md - Curated source code pack

Example

$ code-context-agent analyze /path/to/repo $ code-context-agent analyze . --focus "authentication" $ code-context-agent analyze . --output-dir ./output $ code-context-agent analyze . --issue "gh:1694"

Source code in src/code_context_agent/cli.py
@app.command
def analyze(  # noqa: C901, PLR0912
    path: Annotated[
        Path,
        Parameter(help="Path to the repository to analyze."),
    ] = Path(),
    *,
    output_dir: Annotated[
        Path | None,
        Parameter(help="Output directory for context files. Defaults to <repo>/.code-context"),
    ] = None,
    focus: Annotated[
        str,
        Parameter(help="Focus area for analysis (e.g., 'authentication', 'API endpoints', 'database layer')."),
    ] = "",
    issue: Annotated[
        str,
        Parameter(help="Issue reference for focused analysis (e.g., 'gh:1694', 'gh:owner/repo#1694')."),
    ] = "",
    output_format: Annotated[
        Literal["rich", "json"],
        Parameter(help="Output format: rich (default TUI), json (AnalysisResult JSON to stdout)."),
    ] = "rich",
    since: Annotated[
        str,
        Parameter(
            help="Git ref for incremental analysis (e.g., 'HEAD~5', 'main', 'abc123'). "
            "Only re-analyzes files changed since this ref.",
        ),
    ] = "",
    full: Annotated[
        bool,
        Parameter(help="Run exhaustive analysis with no size limits and fail-fast error handling."),
    ] = False,
    quiet: Annotated[
        bool,
        Parameter(help="Suppress all output except errors on stderr. No TUI, no JSON."),
    ] = False,
    debug: Annotated[
        bool,
        Parameter(help="Enable debug logging for troubleshooting."),
    ] = False,
) -> None:
    """Analyze a codebase and produce a narrated context bundle.

    The agent uses LSP, AST-grep, ripgrep, repomix, and git history
    tools to understand the codebase, then produces a narrated markdown
    bundle. Analysis depth is determined automatically based on repository
    size and complexity.

    Outputs:
        .code-context/CONTEXT.md - The narrated context bundle
        .code-context/CONTEXT.orientation.md - Token distribution tree
        .code-context/CONTEXT.bundle.md - Curated source code pack

    Example:
        $ code-context-agent analyze /path/to/repo
        $ code-context-agent analyze . --focus "authentication"
        $ code-context-agent analyze . --output-dir ./output
        $ code-context-agent analyze . --issue "gh:1694"
    """
    import asyncio
    import sys

    from loguru import logger

    from code_context_agent.agent import run_analysis
    from code_context_agent.utils import setup_logger

    # Configure logging: suppress loguru in normal mode (Rich Live conflict),
    # enable full logging in debug mode (which uses QuietConsumer instead)
    if debug:
        setup_logger(level="DEBUG")
        if not quiet:
            console.print("[dim]Debug logging enabled (live display disabled)[/dim]")
    else:
        # Remove default loguru handler to prevent stderr writes that break Rich Live
        logger.remove()
        setup_logger(level="WARNING")

    repo_path = path.resolve()

    if not repo_path.exists():
        print(f"Error: Path does not exist: {repo_path}", file=sys.stderr)
        raise SystemExit(1)

    if not repo_path.is_dir():
        print(f"Error: Path is not a directory: {repo_path}", file=sys.stderr)
        raise SystemExit(1)

    # Validate flag combinations
    _validate_flags(full=full, since=since)

    # Derive analysis mode
    mode = _derive_mode(full=full, focus=focus, since=since)

    # Auto-preflight in full mode
    if full and not quiet:
        preflight = _preflight_check()
        missing = [name for name, info in preflight.items() if not info["available"]]
        if missing:
            console.print(f"[yellow]Warning:[/yellow] Missing tools: {', '.join(missing)}")
            console.print("  Full mode works best with all tools installed.")

    # Show analysis configuration
    if not quiet:
        console.print()
        console.print("[bold]Code Context Analysis[/bold]")
        console.print(f"  Repository: [cyan]{repo_path}[/cyan]")
        if focus:
            console.print(f"  Focus: [magenta]{focus}[/magenta]")
        if full:
            console.print("  Mode: [bold magenta]FULL (exhaustive)[/bold magenta]")
        console.print()

    # In debug mode or JSON mode, use quiet consumer (log output replaces Live display)
    use_quiet = quiet or debug or (output_format == "json")

    # Fetch issue context if provided (deterministic, not model-invoked)
    issue_context = _fetch_issue_context(issue, quiet=use_quiet) if issue else None

    # Build incremental analysis context if --since provided
    since_context = None
    if since:
        effective_output = output_dir or repo_path / DEFAULT_OUTPUT_DIR
        since_context = _build_since_context(repo_path, since, effective_output)
    if since_context and not use_quiet:
        console.print(f"  Incremental: [magenta]since {since}[/magenta]")

    # Run the analysis
    result = asyncio.run(
        run_analysis(
            repo_path=repo_path,
            output_dir=output_dir,
            focus=focus or None,
            quiet=use_quiet,
            issue_context=issue_context,
            since_context=since_context,
            mode=mode,
        ),
    )

    if output_format == "json":
        _display_result_json(result)
    else:
        _display_result(result, debug=debug, quiet=quiet)

viz

viz(
    path=Path(),
    *,
    output_dir=None,
    port=8765,
    no_open=False,
)

Launch an interactive visualization of analysis results.

Serves a local web UI that displays the code graph, modules, hotspots, dependency chains, and the CONTEXT.md narrative.

Requires a prior analyze run to generate .code-context/ output files.

Example

$ code-context-agent viz /path/to/repo $ code-context-agent viz . --port 9000

Source code in src/code_context_agent/cli.py
@app.command
def viz(  # noqa: C901
    path: Annotated[
        Path,
        Parameter(help="Path to the repository (must contain .code-context/ output)."),
    ] = Path(),
    *,
    output_dir: Annotated[
        Path | None,
        Parameter(help="Output directory containing analysis results. Defaults to <path>/.code-context"),
    ] = None,
    port: Annotated[
        int,
        Parameter(help="Port for the local HTTP server."),
    ] = 8765,
    no_open: Annotated[
        bool,
        Parameter(help="Don't auto-open the browser."),
    ] = False,
) -> None:
    """Launch an interactive visualization of analysis results.

    Serves a local web UI that displays the code graph, modules,
    hotspots, dependency chains, and the CONTEXT.md narrative.

    Requires a prior `analyze` run to generate .code-context/ output files.

    Example:
        $ code-context-agent viz /path/to/repo
        $ code-context-agent viz . --port 9000
    """
    import http.server
    import socketserver
    import threading
    import webbrowser

    repo_path = path.resolve()
    agent_dir = (output_dir or repo_path / DEFAULT_OUTPUT_DIR).resolve()

    if not agent_dir.exists():
        console.print(f"[red]Error:[/red] No analysis output found at {agent_dir}")
        console.print("Run [cyan]code-context-agent analyze[/cyan] first.")
        raise SystemExit(1)

    # Resolve viz directory (shipped inside the package)
    viz_dir = Path(__file__).parent / "viz"
    if not viz_dir.exists():
        console.print("[red]Error:[/red] Visualization files not found.")
        raise SystemExit(1)

    # Build URL params pointing to the agent output files
    params = []
    graph_file = agent_dir / "code_graph.json"
    context_file = agent_dir / "CONTEXT.md"
    result_file = agent_dir / "analysis_result.json"

    # Check what files exist
    files_found = []
    if graph_file.exists():
        params.append("graph=/data/code_graph.json")
        files_found.append("code_graph.json")
    if context_file.exists():
        params.append("narrative=/data/CONTEXT.md")
        files_found.append("CONTEXT.md")
    if result_file.exists():
        params.append("result=/data/analysis_result.json")
        files_found.append("analysis_result.json")

    if not files_found:
        console.print(f"[yellow]Warning:[/yellow] No analysis files found in {agent_dir}")
        console.print("The visualizer will open but you'll need to load data manually.")

    query = "&".join(params)
    url = f"http://localhost:{port}/{'?' + query if query else ''}"

    # Custom handler that serves viz files and proxies /data/ to agent_dir
    class VizHandler(http.server.SimpleHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, directory=str(viz_dir), **kwargs)

        def translate_path(self, path):
            """Route /data/ requests to the agent output directory."""
            if path.startswith("/data/"):
                relative = path[6:]  # strip /data/
                resolved = (agent_dir / relative).resolve()
                # Prevent path traversal outside agent_dir
                if not str(resolved).startswith(str(agent_dir)):
                    return str(agent_dir / "404")
                return str(resolved)
            return super().translate_path(path)

        def log_message(self, format, *args):
            pass  # Suppress request logs

    console.print()
    console.print("[bold]Code Context Visualizer[/bold]")
    console.print(f"  Data: [cyan]{agent_dir}[/cyan]")
    console.print(f"  Files: {', '.join(files_found) or 'none'}")
    console.print(f"  URL: [link={url}]{url}[/link]")
    console.print()
    console.print("[dim]Press Ctrl+C to stop[/dim]")

    if not no_open:
        threading.Timer(0.5, lambda: webbrowser.open(url)).start()

    with socketserver.TCPServer(("127.0.0.1", port), VizHandler) as httpd:
        httpd.allow_reuse_address = True
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            console.print("\n[dim]Server stopped[/dim]")

serve

serve(*, transport='stdio', host='127.0.0.1', port=8000)

Start the MCP server exposing code-context-agent's analysis capabilities.

Exposes the core differentiators — full analysis pipeline, code graph algorithms, and progressive exploration — via the Model Context Protocol.

Transports

stdio: For Claude Desktop, Claude Code, and local MCP clients (default) http: For networked/multi-client access (Streamable HTTP) sse: For legacy MCP clients only

Example

$ code-context-agent serve # stdio for Claude Desktop $ code-context-agent serve --transport http # HTTP on localhost:8000 $ code-context-agent serve --transport http --port 9000

Source code in src/code_context_agent/cli.py
@app.command
def serve(
    *,
    transport: Annotated[
        Literal["stdio", "http", "sse"],
        Parameter(help="MCP transport: stdio (default, for Claude Desktop/CLI), http (networked), sse (legacy)."),
    ] = "stdio",
    host: Annotated[
        str,
        Parameter(help="Host to bind for http/sse transport."),
    ] = "127.0.0.1",
    port: Annotated[
        int,
        Parameter(help="Port for http/sse transport."),
    ] = 8000,
) -> None:
    """Start the MCP server exposing code-context-agent's analysis capabilities.

    Exposes the core differentiators — full analysis pipeline, code graph
    algorithms, and progressive exploration — via the Model Context Protocol.

    Transports:
        stdio: For Claude Desktop, Claude Code, and local MCP clients (default)
        http:  For networked/multi-client access (Streamable HTTP)
        sse:   For legacy MCP clients only

    Example:
        $ code-context-agent serve                          # stdio for Claude Desktop
        $ code-context-agent serve --transport http         # HTTP on localhost:8000
        $ code-context-agent serve --transport http --port 9000
    """
    from code_context_agent.mcp import mcp as mcp_server

    if transport == "stdio":
        console.print("[dim]Starting MCP server (stdio transport)...[/dim]")
    else:
        console.print(f"[dim]Starting MCP server ({transport} transport on {host}:{port})...[/dim]")

    mcp_server.run(transport=transport, host=host, port=port)

check

check()

Check availability of external tool dependencies.

Verifies that ripgrep, ast-grep, repomix, and npx are installed and accessible. Useful for diagnosing setup issues before running analysis.

Example

$ code-context-agent check

Source code in src/code_context_agent/cli.py
@app.command
def check() -> None:
    """Check availability of external tool dependencies.

    Verifies that ripgrep, ast-grep, repomix, and npx are installed
    and accessible. Useful for diagnosing setup issues before running
    analysis.

    Example:
        $ code-context-agent check
    """
    preflight = _preflight_check()
    all_ok = True
    for name, info in preflight.items():
        if info["available"]:
            console.print(f"  [green]\u2713[/green] {name}")
        else:
            console.print(f"  [red]\u2717[/red] {name} \u2014 install via {info['package']}")
            all_ok = False
    if all_ok:
        console.print("\n[green]All tools available.[/green]")
    else:
        console.print("\n[yellow]Some tools are missing. Analysis may be limited.[/yellow]")
        raise SystemExit(1)