- Apply black formatting to all Python files - Fix various flake8 violations in agent system code - Clean up imports and whitespace issues 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
626 lines
18 KiB
Python
626 lines
18 KiB
Python
"""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 <repository-url>
|
|
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)
|