"""Agent installation and management utilities.""" import shutil import json from pathlib import Path from typing import List, Dict, Optional from dataclasses import dataclass from .registry import AgentRegistry @dataclass class InstallationConfig: """Configuration for agent installation.""" target_dir: Path claude_config_path: Optional[Path] = None makefile_path: Optional[Path] = None update_docs: bool = True create_backup: bool = True class AgentInstaller: """Handles installation and management of agents in projects.""" def __init__(self, registry: AgentRegistry): self.registry = registry def install_agents( self, agent_names: List[str], config: InstallationConfig ) -> Dict[str, str]: """Install agents into a project. Returns: Dict mapping agent names to installation status """ results = {} # Resolve dependencies resolved_agents = self.registry.resolve_dependencies(agent_names) # Create target directory if it doesn't exist agents_dir = config.target_dir / "agents" agents_dir.mkdir(parents=True, exist_ok=True) # Create backup if requested if config.create_backup and agents_dir.exists(): self._create_backup(agents_dir) # Install each agent for agent_name in resolved_agents: try: agent = self.registry.get_agent(agent_name) if not agent: results[agent_name] = "ERROR: Agent not found" continue target_path = agents_dir / f"agent-{agent_name}.md" shutil.copy2(agent.file_path, target_path) results[agent_name] = "INSTALLED" except Exception as e: results[agent_name] = f"ERROR: {str(e)}" # Update configuration files if config.claude_config_path: self._update_claude_config(resolved_agents, config.claude_config_path) if config.makefile_path and config.makefile_path.exists(): self._update_makefile(resolved_agents, config.makefile_path) if config.update_docs: self._update_documentation(resolved_agents, config.target_dir) return results def list_installed_agents(self, project_dir: Path) -> List[str]: """List agents currently installed in a project.""" agents_dir = project_dir / "agents" if not agents_dir.exists(): return [] installed = [] for agent_file in agents_dir.glob("agent-*.md"): agent_name = agent_file.stem.replace("agent-", "") installed.append(agent_name) return sorted(installed) def update_agents( self, project_dir: Path, agent_names: Optional[List[str]] = None ) -> Dict[str, str]: """Update installed agents to latest versions.""" if agent_names is None: agent_names = self.list_installed_agents(project_dir) config = InstallationConfig(target_dir=project_dir) return self.install_agents(agent_names, config) def remove_agents( self, agent_names: List[str], project_dir: Path ) -> Dict[str, str]: """Remove agents from a project.""" results = {} agents_dir = project_dir / "agents" for agent_name in agent_names: try: agent_file = agents_dir / f"agent-{agent_name}.md" if agent_file.exists(): agent_file.unlink() results[agent_name] = "REMOVED" else: results[agent_name] = "NOT_FOUND" except Exception as e: results[agent_name] = f"ERROR: {str(e)}" return results def validate_installation(self, project_dir: Path) -> Dict[str, List[str]]: """Validate agents installed in a project.""" errors = {} agents_dir = project_dir / "agents" if not agents_dir.exists(): return {"project": ["No agents directory found"]} # Check each installed agent for agent_file in agents_dir.glob("agent-*.md"): agent_name = agent_file.stem.replace("agent-", "") agent_errors = [] try: # Check if agent exists in registry (validates the file) agent_def = self.registry.get_agent(agent_name) if not agent_def: agent_errors.append("Agent not found in registry") except Exception as e: agent_errors.append(f"Invalid agent format: {str(e)}") if agent_errors: errors[agent_name] = agent_errors return errors def _create_backup(self, agents_dir: Path): """Create a backup of the existing agents directory.""" import datetime import time # Add microseconds to avoid collisions in tests timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") microseconds = int(time.time() * 1000000) % 1000000 backup_dir = agents_dir.parent / f"agents_backup_{timestamp}_{microseconds}" # Ensure unique backup directory counter = 0 while backup_dir.exists(): counter += 1 backup_dir = ( agents_dir.parent / f"agents_backup_{timestamp}_{microseconds}_{counter}" ) shutil.copytree(agents_dir, backup_dir) print(f"Created backup at: {backup_dir}") def _update_claude_config(self, agent_names: List[str], config_path: Path): """Update Claude Code configuration with agent references.""" try: # Read existing config config = {} if config_path.exists(): with open(config_path, "r") as f: config = json.load(f) # Ensure agents section exists if "agents" not in config: config["agents"] = {} # Add agent references for agent_name in agent_names: config["agents"][agent_name] = { "path": f"agents/agent-{agent_name}.md", "enabled": True, } # Write updated config with open(config_path, "w") as f: json.dump(config, f, indent=2) print(f"Updated Claude configuration: {config_path}") except Exception as e: print(f"Warning: Could not update Claude config: {e}") def _update_makefile(self, agent_names: List[str], makefile_path: Path): """Update Makefile with agent-specific targets.""" try: # Read existing Makefile with open(makefile_path, "r") as f: content = f.read() # Add agent management targets if not present agent_targets = """ # Agent Management Targets agents-list: \t@echo "Installed agents:" \t@ls agents/ 2>/dev/null | grep agent- | sed 's/agent-//g' | sed 's/.md//g' \\ \t|| echo "No agents installed" agents-update: \t@echo "Updating agents..." \t@kaizen-agentic update agents-validate: \t@echo "Validating agents..." \t@kaizen-agentic validate agents/ """ if "agents-list:" not in content: content += agent_targets # Write updated Makefile with open(makefile_path, "w") as f: f.write(content) print(f"Updated Makefile: {makefile_path}") except Exception as e: print(f"Warning: Could not update Makefile: {e}") def _update_documentation(self, agent_names: List[str], project_dir: Path): """Update project documentation with agent information.""" try: claude_md = project_dir / "CLAUDE.md" agent_section = "## Installed Agents\n\n" agent_section += ( "This project includes the following specialized agents:\n\n" ) # Group agents by category categories = {} for agent_name in agent_names: agent = self.registry.get_agent(agent_name) if agent: category = agent.category.value if category not in categories: categories[category] = [] categories[category].append(agent) # Generate documentation for category, agents in categories.items(): agent_section += f"### {category.replace('-', ' ').title()}\n\n" for agent in agents: agent_section += f"- **{agent.name}**: {agent.description}\n" agent_section += "\n" agent_section += ( "Use these agents by referencing them in your " "Claude Code interactions.\n\n" ) # Update or create CLAUDE.md if claude_md.exists(): with open(claude_md, "r") as f: content = f.read() # Replace existing agent section or append if "## Installed Agents" in content: import re content = re.sub( r"## Installed Agents.*?(?=##|\Z)", agent_section, content, flags=re.DOTALL, ) else: content += "\n" + agent_section with open(claude_md, "w") as f: f.write(content) else: # Create new CLAUDE.md header = "# Claude Code Configuration\n\n" header += "This file contains Claude Code configuration and agent information.\n\n" with open(claude_md, "w") as f: f.write(header + agent_section) print(f"Updated documentation: {claude_md}") except Exception as e: print(f"Warning: Could not update documentation: {e}") class ProjectInitializer: """Initializes new projects with agents and templates.""" def __init__(self, registry: AgentRegistry): self.registry = registry def init_project( self, project_dir: Path, template: str = "python-basic", agent_names: Optional[List[str]] = None, project_name: Optional[str] = None, ) -> Dict[str, str]: """Initialize a new project with agents and structure.""" results = {} # Create project directory project_dir.mkdir(parents=True, exist_ok=True) # Get agents for template if agent_names is None: templates = self.registry.get_agent_templates() agent_names = templates.get(template, templates["python-basic"]) # Set up project name if project_name is None: project_name = project_dir.name # Install agents config = InstallationConfig( target_dir=project_dir, claude_config_path=project_dir / "CLAUDE.md", makefile_path=project_dir / "Makefile", ) installer = AgentInstaller(self.registry) install_results = installer.install_agents(agent_names, config) results.update(install_results) # Create basic project structure self._create_project_structure(project_dir, project_name, template) return results def _create_project_structure( self, project_dir: Path, project_name: str, template: str ): """Create basic project structure based on template.""" # Create directories dirs_to_create = ["src", "tests", "docs"] if template.startswith("python"): dirs_to_create.extend( [f"src/{project_name.replace('-', '_')}", ".github/workflows"] ) for dir_name in dirs_to_create: (project_dir / dir_name).mkdir(parents=True, exist_ok=True) # Create basic files self._create_gitignore(project_dir) self._create_readme(project_dir, project_name) self._create_makefile(project_dir, project_name) if template.startswith("python"): self._create_pyproject_toml(project_dir, project_name) self._create_init_py(project_dir, project_name) def _create_gitignore(self, project_dir: Path): """Create .gitignore file.""" gitignore_content = """# Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # Virtual environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # IDEs .vscode/ .idea/ *.swp *.swo *~ # Testing .pytest_cache/ .coverage htmlcov/ .tox/ """ (project_dir / ".gitignore").write_text(gitignore_content) def _create_readme(self, project_dir: Path, project_name: str): """Create README.md file.""" readme_content = f"""# {project_name} A Python project following best practices with Kaizen Agentic agents. ## Setup ```bash # Clone the repository git clone cd {project_name} # Set up development environment make setup-complete # Activate virtual environment source .venv/bin/activate ``` ## Development ```bash # Run tests make test # Check code quality make lint # Format code make format ``` ## Agents This project uses Kaizen Agentic agents for development workflow automation. See CLAUDE.md for agent details and usage. """ (project_dir / "README.md").write_text(readme_content) def _create_pyproject_toml(self, project_dir: Path, project_name: str): """Create pyproject.toml file.""" pyproject_content = f"""[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "{project_name}" version = "0.1.0" description = "A Python project with Kaizen Agentic agents" readme = "README.md" requires-python = ">=3.9" license = {{text = "MIT"}} authors = [ {{name = "Author Name", email = "author@example.com"}} ] dependencies = [] [project.optional-dependencies] dev = [ "pytest>=7.0", "black>=22.0", "flake8>=5.0", "mypy>=1.0", ] # Note: Add kaizen-agentic dependency when published to PyPI # dev = [..., "kaizen-agentic>=0.1.0"] [tool.setuptools.packages.find] where = ["src"] [tool.black] line-length = 88 target-version = ['py39'] [tool.flake8] max-line-length = 100 exclude = [".git", "__pycache__", "build", "dist"] [tool.mypy] python_version = "3.9" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] """ (project_dir / "pyproject.toml").write_text(pyproject_content) def _create_init_py(self, project_dir: Path, project_name: str): """Create package __init__.py file.""" package_name = project_name.replace("-", "_") init_content = f'''""" {project_name} - A Python project with Kaizen Agentic agents. """ __version__ = "0.1.0" ''' (project_dir / f"src/{package_name}/__init__.py").write_text(init_content) def _create_makefile(self, project_dir: Path, project_name: str): """Create Makefile with standard targets.""" package_name = project_name.replace("-", "_") makefile_content = f"""# {project_name} - Makefile for development workflow # Generated by Kaizen Agentic .PHONY: help setup-complete setup-python setup-tools test lint format clean agents-status agents-update # Default target help: @echo "Available targets:" @echo " setup-complete - Complete development environment setup" @echo " setup-python - Set up Python virtual environment and dependencies" @echo " setup-tools - Install development tools" @echo " test - Run test suite" @echo " lint - Run code quality checks" @echo " format - Format code with black" @echo " clean - Clean build artifacts" @echo " agents-status - Show installed agents status" @echo " agents-update - Update agents to latest versions" # Virtual environment detection VENV := .venv PYTHON := $(VENV)/bin/python PIP := $(VENV)/bin/pip # Complete setup setup-complete: setup-python setup-tools @echo "✅ Development environment setup complete!" @echo "Next steps:" @echo " source $(VENV)/bin/activate # Activate virtual environment" @echo " make test # Run tests" @echo " make lint # Check code quality" # Python environment setup setup-python: $(VENV)/bin/activate $(VENV)/bin/activate: pyproject.toml python3 -m venv $(VENV) $(PIP) install --upgrade pip $(PIP) install -e ".[dev]" touch $(VENV)/bin/activate # Development tools setup setup-tools: $(VENV)/bin/activate @echo "Development tools installed via pyproject.toml" # Testing test: $(VENV)/bin/activate $(PYTHON) -m pytest tests/ -v test-coverage: $(VENV)/bin/activate $(PYTHON) -m pytest tests/ --cov=src/{package_name} --cov-report=html --cov-report=term-missing # Code quality lint: $(VENV)/bin/activate $(PYTHON) -m flake8 src/ tests/ $(PYTHON) -m mypy src/ format: $(VENV)/bin/activate $(PYTHON) -m black src/ tests/ format-check: $(VENV)/bin/activate $(PYTHON) -m black --check src/ tests/ # Cleanup clean: rm -rf build/ rm -rf dist/ rm -rf *.egg-info/ rm -rf .pytest_cache/ rm -rf .coverage rm -rf htmlcov/ find . -type d -name __pycache__ -exec rm -rf {{}} + find . -type f -name "*.pyc" -delete # Agent management agents-status: @if command -v kaizen-agentic >/dev/null 2>&1; then \\ kaizen-agentic status; \\ else \\ echo "kaizen-agentic not found. Install with: pip install kaizen-agentic"; \\ fi agents-update: @if command -v kaizen-agentic >/dev/null 2>&1; then \\ kaizen-agentic update; \\ else \\ echo "kaizen-agentic not found. Install with: pip install kaizen-agentic"; \\ fi agents-list: @if command -v kaizen-agentic >/dev/null 2>&1; then \\ kaizen-agentic list; \\ else \\ echo "kaizen-agentic not found. Install with: pip install kaizen-agentic"; \\ fi agents-validate: @if command -v kaizen-agentic >/dev/null 2>&1; then \\ kaizen-agentic validate; \\ else \\ echo "kaizen-agentic not found. Install with: pip install kaizen-agentic"; \\ fi """ (project_dir / "Makefile").write_text(makefile_content)