#!/usr/bin/env python3 """ Analyze development environment and find cleanable resources. Checks: - Docker (images, containers, volumes, build cache) - Homebrew cache - npm cache - pip cache - Old .git directories in archived projects Usage: python3 analyze_dev_env.py """ import os import sys import subprocess import json from pathlib import Path def format_size(bytes_size): """Convert bytes to human-readable format.""" if bytes_size is None: return "Unknown" for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes_size < 1024.0: return f"{bytes_size:.1f} {unit}" bytes_size /= 1024.0 return f"{bytes_size:.1f} PB" def run_command(cmd): """Run command and return output, or None if error.""" try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=30 ) if result.returncode == 0: return result.stdout.strip() return None except (subprocess.TimeoutExpired, FileNotFoundError): return None def get_dir_size(path): """Get directory size using du command.""" output = run_command(['du', '-sk', path]) if output: try: size_kb = int(output.split()[0]) return size_kb * 1024 # Convert to bytes except (ValueError, IndexError): pass return 0 def check_docker(): """Check Docker resources.""" print("\n🐳 Docker Resources") print("=" * 50) # Check if Docker is installed if not run_command(['which', 'docker']): print(" Docker not installed or not in PATH") return 0 # Check if Docker daemon is running if not run_command(['docker', 'info']): print(" Docker daemon not running") return 0 total_size = 0 # Images images_output = run_command(['docker', 'images', '-q']) if images_output: image_count = len(images_output.split('\n')) print(f"\nšŸ“¦ Images: {image_count}") # Get size estimate system_output = run_command(['docker', 'system', 'df', '--format', '{{json .}}']) if system_output: for line in system_output.split('\n'): try: data = json.loads(line) if data.get('Type') == 'Images': size_str = data.get('Size', '') # Parse size (format like "1.2GB") if 'GB' in size_str: size = float(size_str.replace('GB', '')) * 1024 * 1024 * 1024 elif 'MB' in size_str: size = float(size_str.replace('MB', '')) * 1024 * 1024 else: size = 0 print(f" Total size: {format_size(size)}") total_size += size except (json.JSONDecodeError, ValueError): pass # Containers containers_output = run_command(['docker', 'ps', '-a', '-q']) if containers_output: container_count = len(containers_output.split('\n')) stopped = run_command(['docker', 'ps', '-a', '-f', 'status=exited', '-q']) stopped_count = len(stopped.split('\n')) if stopped else 0 print(f"\nšŸ“¦ Containers: {container_count} total, {stopped_count} stopped") # Volumes volumes_output = run_command(['docker', 'volume', 'ls', '-q']) if volumes_output: volume_count = len(volumes_output.split('\n')) print(f"\nšŸ“¦ Volumes: {volume_count}") # List volumes for volume in volumes_output.split('\n')[:5]: # Show first 5 inspect = run_command(['docker', 'volume', 'inspect', volume]) print(f" - {volume}") if volume_count > 5: print(f" ... and {volume_count - 5} more") # Build cache buildx_output = run_command(['docker', 'buildx', 'du']) if buildx_output and 'Total:' in buildx_output: print(f"\nšŸ“¦ Build Cache:") for line in buildx_output.split('\n'): if 'Total:' in line: print(f" {line}") print("\nšŸ’” Cleanup: Remove specific images/volumes by ID/name (see SKILL.md)") print(" āš ļø NEVER use 'docker system prune' -- always specify exact objects") return total_size def check_homebrew(): """Check Homebrew cache.""" print("\nšŸŗ Homebrew") print("=" * 50) if not run_command(['which', 'brew']): print(" Homebrew not installed") return 0 cache_path = run_command(['brew', '--cache']) if cache_path and os.path.exists(cache_path): size = get_dir_size(cache_path) print(f" Cache location: {cache_path}") print(f" Cache size: {format_size(size)}") print(f"\nšŸ’” Cleanup command: brew cleanup -s") return size return 0 def check_npm(): """Check npm cache.""" print("\nšŸ“¦ npm") print("=" * 50) if not run_command(['which', 'npm']): print(" npm not installed") return 0 cache_path = run_command(['npm', 'config', 'get', 'cache']) if cache_path and cache_path != 'undefined' and os.path.exists(cache_path): size = get_dir_size(cache_path) print(f" Cache location: {cache_path}") print(f" Cache size: {format_size(size)}") print(f"\nšŸ’” Cleanup command: npm cache clean --force") return size return 0 def check_pip(): """Check pip cache.""" print("\nšŸ pip") print("=" * 50) # Try pip3 first pip_cmd = 'pip3' if run_command(['which', 'pip3']) else 'pip' if not run_command(['which', pip_cmd]): print(" pip not installed") return 0 cache_dir = run_command([pip_cmd, 'cache', 'dir']) if cache_dir and os.path.exists(cache_dir): size = get_dir_size(cache_dir) print(f" Cache location: {cache_dir}") print(f" Cache size: {format_size(size)}") print(f"\nšŸ’” Cleanup command: {pip_cmd} cache purge") return size return 0 def check_old_git_repos(): """Find large .git directories in archived projects.""" print("\nšŸ“ Old Git Repositories") print("=" * 50) home = Path.home() common_project_dirs = [ home / 'Projects', home / 'workspace', home / 'dev', home / 'src', home / 'code' ] git_repos = [] total_size = 0 for project_dir in common_project_dirs: if not project_dir.exists(): continue # Find .git directories try: result = subprocess.run( ['find', str(project_dir), '-name', '.git', '-type', 'd', '-maxdepth', 3], capture_output=True, text=True, timeout=30 ) if result.returncode == 0: for git_path in result.stdout.strip().split('\n'): if git_path: size = get_dir_size(git_path) if size > 10 * 1024 * 1024: # > 10 MB git_repos.append((git_path, size)) total_size += size except subprocess.TimeoutExpired: continue if git_repos: # Sort by size git_repos.sort(key=lambda x: x[1], reverse=True) print(f" Found {len(git_repos)} .git directories > 10 MB") print(f"\n Top 10 largest:") for path, size in git_repos[:10]: # Get parent directory name (project name) project_name = Path(path).parent.name print(f" - {project_name:<30} {format_size(size)}") print(f"\n Total: {format_size(total_size)}") print(f"\nšŸ’” If these are archived projects, consider:") print(f" 1. Delete .git history: rm -rf /.git") print(f" 2. Or compress entire project: tar -czf archive.tar.gz ") else: print(" No large .git directories found in common project locations") return total_size def main(): print("šŸ” Development Environment Analysis") print("=" * 50) total_savings = 0 # Check each component docker_size = check_docker() brew_size = check_homebrew() npm_size = check_npm() pip_size = check_pip() git_size = check_old_git_repos() # Summary print("\n\nšŸ“Š Summary") print("=" * 50) if docker_size: print(f"Docker: {format_size(docker_size)}") total_savings += docker_size if brew_size: print(f"Homebrew cache: {format_size(brew_size)}") total_savings += brew_size if npm_size: print(f"npm cache: {format_size(npm_size)}") total_savings += npm_size if pip_size: print(f"pip cache: {format_size(pip_size)}") total_savings += pip_size if git_size: print(f"Old .git repos: {format_size(git_size)}") total_savings += git_size print("-" * 50) print(f"Potential savings: {format_size(total_savings)}") print("\nšŸ’” Next Steps:") print(" 1. Review Docker volumes before cleanup (may contain data)") print(" 2. Package manager caches are safe to delete") print(" 3. For .git directories, ensure project is truly archived") return 0 if __name__ == '__main__': sys.exit(main())