From fce6fb3fec7e4634f3789dc732daabfd54ddeea7 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 23:42:00 +0000 Subject: [PATCH] Add codechunk package files --- codechunk/cli.py | 170 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 codechunk/cli.py diff --git a/codechunk/cli.py b/codechunk/cli.py new file mode 100644 index 0000000..8e1316f --- /dev/null +++ b/codechunk/cli.py @@ -0,0 +1,170 @@ +import os +from pathlib import Path +from typing import Optional, List + +import click +from rich.console import Console +from rich.panel import Panel +from rich.text import Text + +from codechunk.config import Config, load_config +from codechunk.core.chunking import CodeChunker +from codechunk.core.parser import CodeParser +from codechunk.core.formatter import OutputFormatter +from codechunk.core.dependency import DependencyAnalyzer +from codechunk.utils.logger import get_logger + +console = Console() +logger = get_logger(__name__) + + +@click.group() +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +@click.option("--config", "-c", type=click.Path(), help="Path to config file") +@click.pass_context +def main(ctx: click.Context, verbose: bool, config: Optional[str]) -> None: + ctx.ensure_object(dict) + ctx.obj["verbose"] = verbose + ctx.obj["config_path"] = config + + if verbose: + logger.setLevel("DEBUG") + + +@main.command() +@click.argument("path", type=click.Path(exists=True, file_okay=False, dir_okay=True)) +@click.option("--output", "-o", type=click.Path(), help="Output file path") +@click.option("--format", "-f", type=click.Choice(["ollama", "lmstudio", "markdown"]), default="markdown", + help="Output format") +@click.option("--max-tokens", "-t", type=int, default=8192, help="Maximum token limit") +@click.option("--include", multiple=True, help="File patterns to include") +@click.option("--exclude", multiple=True, help="File patterns to exclude") +@click.pass_context +def generate(ctx: click.Context, path: str, output: Optional[str], format: str, + max_tokens: int, include: tuple, exclude: tuple) -> None: + """Generate optimized context bundle for LLM.""" + config_path = ctx.obj.get("config_path") + verbose = ctx.obj.get("verbose", False) + + try: + config = load_config(config_path) if config_path else Config() + if include: + config.chunking.include_patterns = list(include) + if exclude: + config.chunking.exclude_patterns = list(exclude) + + project_path = Path(path) + logger.info(f"Analyzing project: {project_path}") + + parser = CodeParser() + parser.discover_files(project_path, config.chunking.include_patterns, + config.chunking.exclude_patterns) + + if verbose: + console.print(f"[cyan]Found {len(parser.files)} files to process[/cyan]") + + chunks = parser.parse_all() + + chunker = CodeChunker(config.chunking) + chunks = chunker.chunk_all(chunks) + + analyzer = DependencyAnalyzer() + analyzer.analyze_dependencies(chunks, parser.files) + + formatter = OutputFormatter(format, max_tokens) + formatted_output = formatter.format(chunks) + + if output: + output_path = Path(output) + output_path.write_text(formatted_output) + console.print(f"[green]Context written to: {output_path}[/green]") + else: + console.print(Panel(formatted_output, title="Generated Context", expand=False)) + + console.print(f"\n[cyan]Summary:[/cyan]") + console.print(f" - Files processed: {len(parser.files)}") + console.print(f" - Chunks generated: {len(chunks)}") + console.print(f" - Estimated tokens: {formatter.estimate_tokens(formatted_output)}") + + except Exception as e: + logger.error(f"Error generating context: {e}") + console.print(f"[red]Error: {e}[/red]") + raise click.Abort() + + +@main.command() +@click.argument("path", type=click.Path(exists=True, file_okay=False, dir_okay=True)) +@click.option("--json", is_flag=True, help="Output in JSON format") +@click.pass_context +def analyze(ctx: click.Context, path: str, json: bool) -> None: + """Analyze codebase and report statistics.""" + verbose = ctx.obj.get("verbose", False) + + try: + project_path = Path(path) + logger.info(f"Analyzing project: {project_path}") + + config = Config() + parser = CodeParser() + parser.discover_files(project_path, config.chunking.include_patterns, + config.chunking.exclude_patterns) + chunks = parser.parse_all() + + chunker = CodeChunker(config.chunking) + chunks = chunker.chunk_all(chunks) + + stats = { + "total_files": len(parser.files), + "total_chunks": len(chunks), + "files_by_language": {}, + "chunks_by_type": {}, + "total_lines": sum(c.metadata.line_count for c in chunks), + "total_functions": sum(1 for c in chunks if c.chunk_type == "function"), + "total_classes": sum(1 for c in chunks if c.chunk_type == "class"), + } + + for chunk in chunks: + lang = chunk.metadata.language + stats["files_by_language"][lang] = stats["files_by_language"].get(lang, 0) + 1 + stats["chunks_by_type"][chunk.chunk_type] = stats["chunks_by_type"].get(chunk.chunk_type, 0) + 1 + + if json: + import json as json_module + console.print(json_module.dumps(stats, indent=2)) + else: + console.print(Panel( + Text.from_markup(f""" +[b]Project Analysis[/b] + +Total Files: {stats['total_files']} +Total Chunks: {stats['total_chunks']} +Total Lines: {stats['total_lines']} +Total Functions: {stats['total_functions']} +Total Classes: {stats['total_classes']} + +[b]Files by Language[/b] +{chr(10).join(f' - {lang}: {count}' for lang, count in stats['files_by_language'].items())} + +[b]Chunks by Type[/b] +{chr(10).join(f' - {type_}: {count}' for type_, count in stats['chunks_by_type'].items())} + """), + title="Analysis Results", + expand=False + )) + + except Exception as e: + logger.error(f"Error analyzing project: {e}") + console.print(f"[red]Error: {e}[/red]") + raise click.Abort() + + +@main.command() +@click.pass_context +def version(ctx: click.Context) -> None: + """Show version information.""" + from codechunk import __version__ + console.print(f"[cyan]CodeChunk CLI v{__version__}[/cyan]") + + +if __name__ == "__main__": + main()