#!/usr/bin/env python3 """ Browse and analyze session history. Usage: python3 history-browser.py [--list|--show |--stats|--unsummarized] """ import argparse import json import sys from datetime import datetime from pathlib import Path from typing import Dict, List, Optional CLAUDE_DIR = Path.home() / ".claude" HISTORY_DIR = CLAUDE_DIR / "state" / "personal-assistant" / "history" def load_index() -> Optional[Dict]: """Load history index.""" index_path = HISTORY_DIR / "index.json" try: with open(index_path) as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return None def list_sessions(limit: int = 20, show_all: bool = False): """List recent sessions.""" index = load_index() if not index or "sessions" not in index: print("No sessions found.") return sessions = index["sessions"] if not show_all: sessions = sessions[-limit:] print(f"\nšŸ“œ Session History ({len(sessions)} of {len(index['sessions'])} total)\n") print(f"{'ID':<12} {'Date':<12} {'Summarized':<12} {'Topics'}") print("-" * 70) for session in reversed(sessions): sid = session.get("id", "unknown")[:10] date = session.get("date", "unknown")[:10] summarized = "āœ“" if session.get("summarized", False) else "ā—‹" topics = ", ".join(session.get("topics", [])[:3]) if len(session.get("topics", [])) > 3: topics += "..." print(f"{sid:<12} {date:<12} {summarized:<12} {topics}") print("") def show_session(session_id: str): """Show details for a specific session.""" index = load_index() if not index or "sessions" not in index: print("No sessions found.") return # Find session by ID prefix session = None for s in index["sessions"]: if s.get("id", "").startswith(session_id): session = s break if not session: print(f"Session '{session_id}' not found.") return print(f"\nšŸ“œ Session: {session.get('id', 'unknown')}") print(f" Date: {session.get('date', 'unknown')}") print(f" Summarized: {'Yes' if session.get('summarized') else 'No'}") print(f"\n Topics:") for topic in session.get("topics", []): print(f" - {topic}") print(f"\n Summary:") print(f" {session.get('summary', 'No summary available')}") # Check for associated JSONL file jsonl_path = HISTORY_DIR / f"{session.get('id', '')}.jsonl" if jsonl_path.exists(): print(f"\n Log file: {jsonl_path}") try: with open(jsonl_path) as f: lines = f.readlines() print(f" Log entries: {len(lines)}") except Exception: pass print("") def show_stats(): """Show session statistics.""" index = load_index() if not index or "sessions" not in index: print("No sessions found.") return sessions = index["sessions"] total = len(sessions) summarized = sum(1 for s in sessions if s.get("summarized", False)) unsummarized = total - summarized # Topic frequency topic_counts: Dict[str, int] = {} for s in sessions: for topic in s.get("topics", []): topic_counts[topic] = topic_counts.get(topic, 0) + 1 top_topics = sorted(topic_counts.items(), key=lambda x: x[1], reverse=True)[:10] # Date range dates = [s.get("date", "") for s in sessions if s.get("date")] oldest = min(dates)[:10] if dates else "N/A" newest = max(dates)[:10] if dates else "N/A" print(f"\nšŸ“Š Session Statistics\n") print(f" Total sessions: {total}") print(f" Summarized: {summarized}") print(f" Pending summary: {unsummarized}") print(f" Date range: {oldest} to {newest}") print(f"\n Top Topics:") for topic, count in top_topics: bar = "ā–ˆ" * min(count, 20) print(f" {topic:<30} {bar} ({count})") print("") def list_unsummarized(): """List sessions that need summarization.""" index = load_index() if not index or "sessions" not in index: print("No sessions found.") return unsummarized = [s for s in index["sessions"] if not s.get("summarized", False)] if not unsummarized: print("\nāœ“ All sessions are summarized!\n") return print(f"\n⚠ Unsummarized Sessions ({len(unsummarized)})\n") print(f"{'ID':<12} {'Date':<12} {'Topics'}") print("-" * 60) for session in unsummarized: sid = session.get("id", "unknown")[:10] date = session.get("date", "unknown")[:10] topics = ", ".join(session.get("topics", [])[:3]) or "No topics" print(f"{sid:<12} {date:<12} {topics}") print(f"\nRun /summarize to process these sessions.\n") def main(): parser = argparse.ArgumentParser(description="Browse session history") parser.add_argument("--list", "-l", action="store_true", help="List recent sessions") parser.add_argument("--all", "-a", action="store_true", help="Show all sessions (with --list)") parser.add_argument("--show", "-s", type=str, help="Show session details by ID") parser.add_argument("--stats", action="store_true", help="Show statistics") parser.add_argument("--unsummarized", "-u", action="store_true", help="List unsummarized sessions") parser.add_argument("--limit", "-n", type=int, default=20, help="Number of sessions to show (default: 20)") args = parser.parse_args() if args.show: show_session(args.show) elif args.stats: show_stats() elif args.unsummarized: list_unsummarized() elif args.list or not any([args.show, args.stats, args.unsummarized]): list_sessions(limit=args.limit, show_all=args.all) if __name__ == "__main__": main()