diff --git a/.termflow/commands/visualize.py b/.termflow/commands/visualize.py new file mode 100644 index 0000000..d6c3a87 --- /dev/null +++ b/.termflow/commands/visualize.py @@ -0,0 +1,186 @@ +"""Visualize command for CLI.""" + +from pathlib import Path +from typing import Optional + +import click +from rich.console import Console +from rich.panel import Panel +from rich.text import Text + +from ..core.database import SessionDatabase +from ..core.session import Session +from ..parsers.git_parser import GitLogParser +from ..parsers.pattern_analyzer import PatternDetector, SequenceAnalyzer +from ..exporters.ascii_generator import ASCIIGenerator, GitGraphGenerator +from ..exporters.mermaid_generator import MermaidGenerator + +console = Console() + + +@click.command(name="visualize") +@click.argument("source", type=str) +@click.option( + "--session-id", + "-s", + type=int, + help="Session ID to visualize", +) +@click.option( + "--style", + "visual_style", + type=click.Choice(["compact", "detailed", "minimal"]), + default="detailed", + help="Visualization style", +) +@click.option( + "--format", + "-f", + type=click.Choice(["ascii", "mermaid"]), + default="ascii", + help="Output format", +) +@click.option( + "--output", + "-o", + type=click.Path(path_type=Path), + help="Output file", +) +@click.option( + "--analyze/--no-analyze", + default=True, + help="Include pattern analysis", +) +@click.pass_context +def visualize_cmd( + ctx: click.Context, + source: str, + session_id: Optional[int], + visual_style: str, + format: str, + output: Optional[Path], + analyze: bool, +) -> None: + """Visualize a session or git workflow.""" + db_path = ctx.obj.get("db", Path.home() / ".termflow" / "sessions.db") + + if source == "session": + _visualize_session( + db_path, + session_id, + visual_style, + format, + output, + analyze, + ) + elif source == "git": + _visualize_git( + Path.cwd(), + visual_style, + format, + output, + ) + else: + console.print(f"[red]Unknown source: {source}[/red]") + console.print("Use 'session' or 'git' as source") + + +def _visualize_session( + db_path: Path, + session_id: Optional[int], + style: str, + format: str, + output: Optional[Path], + analyze: bool, +) -> None: + """Visualize a terminal session.""" + db = SessionDatabase(db_path) + + if session_id is None: + sessions = db.get_all_sessions() + if not sessions: + console.print("[yellow]No sessions found[/yellow]") + return + session_id = sessions[0]["id"] + + session = db.get_session(session_id) + if not session: + console.print(f"[red]Session {session_id} not found[/red]") + return + + commands = db.get_session_commands(session_id) + command_list = [cmd.get("command", "") for cmd in commands] + + if analyze: + detector = PatternDetector() + patterns = detector.analyze_commands(command_list) + + if patterns: + text = Text() + text.append(f"Found {len(patterns)} patterns:\n", style="bold") + for i, pattern in enumerate(patterns[:5], 1): + text.append(f" {i}. {pattern.commands} (x{pattern.frequency})\n") + console.print(Panel(text, title="Pattern Analysis")) + + metadata = { + "title": f"Session: {session.get('name', 'Unknown')}", + "command_count": len(command_list), + "session_name": session.get("name", ""), + } + + if format == "ascii": + ascii_gen = ASCIIGenerator(style=style) + result = ascii_gen.generate_from_commands(command_list, metadata) + else: + mermaid_gen = MermaidGenerator() + result = mermaid_gen.generate_flowchart( + command_list, + title=f"Session: {session.get('name', 'Unknown')}", + metadata=metadata, + ) + + if output: + with open(output, "w") as f: + f.write(result) + console.print(f"[cyan]Visualization saved to {output}[/cyan]") + else: + console.print(Panel(result, title=f"Session Flow: {session.get('name', 'Unknown')}")) + + +def _visualize_git( + repo_path: Path, + style: str, + format: str, + output: Optional[Path], +) -> None: + """Visualize git workflow.""" + parser = GitLogParser(repo_path) + + if not parser.is_git_repo(): + console.print("[red]Not a git repository[/red]") + return + + commits = parser.parse_log(max_count=50) + parser.get_commit_parents() + workflow = parser.analyze_workflow() + + if format == "ascii": + graph_gen = GitGraphGenerator() + result = graph_gen.generate_from_parser(parser) + else: + mermaid_gen = MermaidGenerator() + result = mermaid_gen.generate_git_graph_from_parser(parser) + + stats_text = Text() + stats_text.append(f"Commits: {len(commits)}\n", style="bold") + stats_text.append(f"Merges: {workflow.merge_count}\n") + stats_text.append(f"Features: {workflow.feature_count}\n") + stats_text.append(f"Branches: {len(workflow.branches)}") + + if output: + with open(output, "w") as f: + f.write(result) + console.print(f"[cyan]Visualization saved to {output}[/cyan]") + else: + console.print(Panel(result, title="Git Workflow")) + console.print(Panel(stats_text, title="Statistics"))