diff --git a/src/extract_claude_logs.py b/src/extract_claude_logs.py index 8430825..a65820a 100644 --- a/src/extract_claude_logs.py +++ b/src/extract_claude_logs.py @@ -14,6 +14,22 @@ from typing import Dict, List, Optional, Tuple +# Constants +INDENT_NUMBER = 2 +MAJOR_SEPARATOR_WIDTH = 60 +MINOR_SEPARATOR_WIDTH = 40 +SESSION_ID_MAX_LENGTH = 8 +LINES_SHOWN_MESSAGE = 8 +LINES_PER_PAGE_MESSAGE = 30 +MAX_LINES_PER_MESSAGE_DISPLAY = 50 +MAX_LINE_LENGTH_DISPLAY = 100 +MIN_PREVIEW_TEXT_LENGTH = 3 +PREVIEW_TEXT_TRUNCATE_LENGTH = 100 +PREVIEW_ERROR_TRUNCATE_LENGTH = 30 +LIST_SEPARATOR_WIDTH = 80 +SEARCH_MAX_RESULTS_DEFAULT = 30 + + class ClaudeConversationExtractor: """Extract and convert Claude Code conversations from JSONL to markdown.""" @@ -122,7 +138,7 @@ def extract_conversation(self, jsonl_path: Path, detailed: bool = False) -> List conversation.append( { "role": "tool_use", - "content": f"šŸ”§ Tool: {tool_name}\nInput: {json.dumps(tool_input, indent=2)}", + "content": f"šŸ”§ Tool: {tool_name}\nInput: {json.dumps(tool_input, indent=INDENT_NUMBER)}", "timestamp": entry.get("timestamp", ""), } ) @@ -183,7 +199,7 @@ def _extract_text_content(self, content, detailed: bool = False) -> str: tool_name = item.get("name", "unknown") tool_input = item.get("input", {}) text_parts.append(f"\nšŸ”§ Using tool: {tool_name}") - text_parts.append(f"Input: {json.dumps(tool_input, indent=2)}\n") + text_parts.append(f"Input: {json.dumps(tool_input, indent=INDENT_NUMBER)}\n") return "\n".join(text_parts) else: return str(content) @@ -208,9 +224,9 @@ def display_conversation(self, jsonl_path: Path, detailed: bool = False) -> None # Clear screen and show header print("\033[2J\033[H", end="") # Clear screen - print("=" * 60) + print("=" * MAJOR_SEPARATOR_WIDTH) print(f"šŸ“„ Viewing: {jsonl_path.parent.name}") - print(f"Session: {session_id[:8]}...") + print(f"Session: {session_id[:SESSION_ID_MAX_LENGTH]}...") # Get timestamp from first message first_timestamp = messages[0].get("timestamp", "") @@ -221,12 +237,12 @@ def display_conversation(self, jsonl_path: Path, detailed: bool = False) -> None except Exception: pass - print("=" * 60) + print("=" * MAJOR_SEPARATOR_WIDTH) print("↑↓ to scroll • Q to quit • Enter to continue\n") # Display messages with pagination - lines_shown = 8 # Header lines - lines_per_page = 30 + lines_shown = LINES_SHOWN_MESSAGE # Header lines + lines_per_page = LINES_PER_PAGE_MESSAGE for i, msg in enumerate(messages): role = msg["role"] @@ -234,13 +250,13 @@ def display_conversation(self, jsonl_path: Path, detailed: bool = False) -> None # Format role display if role == "user" or role == "human": - print(f"\n{'─' * 40}") + print(f"\n{'─' * MINOR_SEPARATOR_WIDTH}") print(f"šŸ‘¤ HUMAN:") - print(f"{'─' * 40}") + print(f"{'─' * MINOR_SEPARATOR_WIDTH}") elif role == "assistant": - print(f"\n{'─' * 40}") + print(f"\n{'─' * MINOR_SEPARATOR_WIDTH}") print(f"šŸ¤– CLAUDE:") - print(f"{'─' * 40}") + print(f"{'─' * MINOR_SEPARATOR_WIDTH}") elif role == "tool_use": print(f"\nšŸ”§ TOOL USE:") elif role == "tool_result": @@ -252,12 +268,12 @@ def display_conversation(self, jsonl_path: Path, detailed: bool = False) -> None # Display content (limit very long messages) lines = content.split('\n') - max_lines_per_msg = 50 + max_lines_per_msg = MAX_LINES_PER_MESSAGE_DISPLAY for line_idx, line in enumerate(lines[:max_lines_per_msg]): # Wrap very long lines - if len(line) > 100: - line = line[:97] + "..." + if len(line) > MAX_LINE_LENGTH_DISPLAY: + line = line[:(MAX_LINE_LENGTH_DISPLAY - 3)] + "..." print(line) lines_shown += 1 @@ -275,9 +291,9 @@ def display_conversation(self, jsonl_path: Path, detailed: bool = False) -> None print(f"... [{len(lines) - max_lines_per_msg} more lines truncated]") lines_shown += 1 - print("\n" + "=" * 60) + print("\n" + "=" * MAJOR_SEPARATOR_WIDTH) print("šŸ“„ End of conversation") - print("=" * 60) + print("=" * MAJOR_SEPARATOR_WIDTH) input("\nPress Enter to continue...") except Exception as e: @@ -306,7 +322,7 @@ def save_as_markdown( date_str = datetime.now().strftime("%Y-%m-%d") time_str = "" - filename = f"claude-conversation-{date_str}-{session_id[:8]}.md" + filename = f"claude-conversation-{date_str}-{session_id[:SESSION_ID_MAX_LENGTH]}.md" output_path = self.output_dir / filename with open(output_path, "w", encoding="utf-8") as f: @@ -361,7 +377,7 @@ def save_as_json( else: date_str = datetime.now().strftime("%Y-%m-%d") - filename = f"claude-conversation-{date_str}-{session_id[:8]}.json" + filename = f"claude-conversation-{date_str}-{session_id[:SESSION_ID_MAX_LENGTH]}.json" output_path = self.output_dir / filename # Create JSON structure @@ -373,7 +389,7 @@ def save_as_json( } with open(output_path, "w", encoding="utf-8") as f: - json.dump(output, f, indent=2, ensure_ascii=False) + json.dump(output, f, indent=INDENT_NUMBER, ensure_ascii=False) return output_path @@ -398,7 +414,7 @@ def save_as_html( date_str = datetime.now().strftime("%Y-%m-%d") time_str = "" - filename = f"claude-conversation-{date_str}-{session_id[:8]}.html" + filename = f"claude-conversation-{date_str}-{session_id[:SESSION_ID_MAX_LENGTH]}.html" output_path = self.output_dir / filename # HTML template with modern styling @@ -407,7 +423,7 @@ def save_as_html( - Claude Conversation - {session_id[:8]} + Claude Conversation - {session_id[:SESSION_ID_MAX_LENGTH]}