| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| echo "🚀 AI-Assisted Development Environment Setup" |
| echo "==============================================" |
|
|
| |
| OS="unknown" |
| case "$(uname -s)" in |
| Linux*) OS="linux";; |
| Darwin*) OS="macos";; |
| CYGWIN*|MINGW*|MSYS*) OS="windows";; |
| esac |
|
|
| echo "Detected OS: $OS" |
|
|
| |
| echo "" |
| echo "📦 Checking Python..." |
| if ! command -v python3 &> /dev/null; then |
| echo "❌ Python 3 not found. Please install Python 3.11+." |
| exit 1 |
| fi |
|
|
| PYTHON_VERSION=$(python3 --version | grep -oE '[0-9]+\.[0-9]+') |
| echo "✓ Python $PYTHON_VERSION found" |
|
|
| |
| echo "" |
| echo "🌿 Setting up virtual environment..." |
| if [ ! -d ".venv" ]; then |
| python3 -m venv .venv |
| echo "✓ Created .venv/" |
| else |
| echo "✓ .venv/ already exists" |
| fi |
|
|
| source .venv/bin/activate |
|
|
| |
| echo "" |
| echo "📥 Installing Python dependencies..." |
| pip install --upgrade pip |
|
|
| |
| pip install fastapi uvicorn pydantic pydantic-settings sqlalchemy pytest pytest-asyncio \ |
| pytest-mock pytest-cov black ruff mypy httpx factory-boy |
|
|
| |
| pip install smolagents openai |
|
|
| echo "✓ Core dependencies installed" |
|
|
| |
| echo "" |
| echo "🦙 Checking Ollama..." |
| if command -v ollama &> /dev/null; then |
| echo "✓ Ollama found" |
| |
| |
| if ollama list | grep -q "gemma4"; then |
| echo "✓ Gemma 4 model available" |
| else |
| echo "⚠ Gemma 4 not found. Pull it with: ollama pull gemma4:4b" |
| echo " (Optional: ollama pull gemma4:9b for stronger reasoning)" |
| fi |
| else |
| echo "⚠ Ollama not found. Install from https://ollama.com" |
| echo " After install, run: ollama pull gemma4:4b" |
| fi |
|
|
| |
| echo "" |
| echo "⬢ Checking Node.js..." |
| if command -v node &> /dev/null; then |
| NODE_VERSION=$(node --version) |
| echo "✓ Node.js $NODE_VERSION found" |
| else |
| echo "⚠ Node.js not found. Install for MCP server support." |
| fi |
|
|
| |
| echo "" |
| echo "🔀 Checking Git..." |
| if [ -d ".git" ]; then |
| echo "✓ Git repository initialized" |
| else |
| git init |
| echo "✓ Initialized git repository" |
| fi |
|
|
| |
| echo "" |
| echo "🪝 Setting up pre-commit..." |
| pip install pre-commit |
| pre-commit install || true |
|
|
| |
| echo "" |
| echo "🔐 Creating .env template..." |
| if [ ! -f ".env" ]; then |
| cat > .env << 'EOF' |
| |
| |
|
|
| APP_NAME="MyApp" |
| DEBUG=true |
| DATABASE_URL="sqlite:///./app.db" |
| SECRET_KEY="change-me-in-production" |
| ALGORITHM="HS256" |
| ACCESS_TOKEN_EXPIRE_MINUTES=30 |
|
|
| |
| HF_TOKEN="" |
|
|
| |
| OLLAMA_BASE_URL="http://localhost:11434" |
| OLLAMA_MODEL="gemma4:4b" |
| EOF |
| echo "✓ Created .env (edit with your values)" |
| else |
| echo "✓ .env already exists" |
| fi |
|
|
| |
| echo "" |
| echo "📁 Initializing project structure..." |
| touch src/__init__.py |
| touch src/core/__init__.py |
| touch src/api/__init__.py |
| touch src/services/__init__.py |
| touch src/utils/__init__.py |
| touch tests/__init__.py |
| touch tests/unit/__init__.py |
| touch tests/integration/__init__.py |
|
|
| echo "✓ Structure initialized" |
|
|
| |
| echo "" |
| echo "📝 Creating starter files..." |
|
|
| |
| cat > src/core/config.py << 'EOF' |
| """Application configuration using pydantic-settings.""" |
|
|
| from functools import lru_cache |
| from pydantic_settings import BaseSettings |
|
|
|
|
| class Settings(BaseSettings): |
| """Application settings loaded from environment variables.""" |
|
|
| app_name: str = "MyApp" |
| debug: bool = False |
| database_url: str = "sqlite:///./app.db" |
| secret_key: str = "change-me" |
| algorithm: str = "HS256" |
| access_token_expire_minutes: int = 30 |
|
|
| class Config: |
| env_file = ".env" |
| env_file_encoding = "utf-8" |
|
|
|
|
| @lru_cache |
| def get_settings() -> Settings: |
| """Return cached Settings instance.""" |
| return Settings() |
| EOF |
|
|
| |
| cat > src/core/models.py << 'EOF' |
| """SQLAlchemy ORM models.""" |
|
|
| from sqlalchemy import Column, Integer, String, DateTime, create_engine |
| from sqlalchemy.ext.declarative import declarative_base |
| from sqlalchemy.sql import func |
|
|
| Base = declarative_base() |
|
|
|
|
| class BaseModel: |
| """Base model with timestamps.""" |
|
|
| id = Column(Integer, primary_key=True, index=True) |
| created_at = Column(DateTime(timezone=True), server_default=func.now()) |
| updated_at = Column(DateTime(timezone=True), onupdate=func.now()) |
| EOF |
|
|
| |
| cat > src/core/database.py << 'EOF' |
| """Database connection and session management.""" |
|
|
| from sqlalchemy import create_engine |
| from sqlalchemy.orm import sessionmaker, Session |
|
|
| from .config import get_settings |
|
|
| settings = get_settings() |
| engine = create_engine( |
| settings.database_url, |
| connect_args={"check_same_thread": False} if "sqlite" in settings.database_url else {}, |
| ) |
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
|
|
|
|
| def get_db() -> Session: |
| """Yield a database session. Use as FastAPI dependency.""" |
| db = SessionLocal() |
| try: |
| yield db |
| finally: |
| db.close() |
| EOF |
|
|
| |
| cat > src/utils/exceptions.py << 'EOF' |
| """Custom application exceptions.""" |
|
|
|
|
| class AppException(Exception): |
| """Base application exception.""" |
|
|
| def __init__(self, message: str, status_code: int = 500) -> None: |
| self.message = message |
| self.status_code = status_code |
| super().__init__(message) |
|
|
|
|
| class ValidationError(AppException): |
| """Input validation failed.""" |
|
|
| def __init__(self, message: str) -> None: |
| super().__init__(message, status_code=400) |
|
|
|
|
| class NotFoundError(AppException): |
| """Resource not found.""" |
|
|
| def __init__(self, message: str) -> None: |
| super().__init__(message, status_code=404) |
|
|
|
|
| class AuthenticationError(AppException): |
| """Authentication failed.""" |
|
|
| def __init__(self, message: str = "Authentication failed") -> None: |
| super().__init__(message, status_code=401) |
| EOF |
|
|
| |
| cat > src/utils/logger.py << 'EOF' |
| """Structured logging configuration.""" |
|
|
| import logging |
| import sys |
| from typing import Any |
|
|
|
|
| def get_logger(name: str) -> logging.Logger: |
| """Return a configured logger.""" |
| logger = logging.getLogger(name) |
| if not logger.handlers: |
| handler = logging.StreamHandler(sys.stdout) |
| formatter = logging.Formatter( |
| "%(asctime)s | %(name)s | %(levelname)s | %(message)s" |
| ) |
| handler.setFormatter(formatter) |
| logger.addHandler(handler) |
| logger.setLevel(logging.INFO) |
| return logger |
| EOF |
|
|
| |
| cat > src/main.py << 'EOF' |
| """FastAPI application entry point.""" |
|
|
| from fastapi import FastAPI |
| from contextlib import asynccontextmanager |
|
|
| from .core.config import get_settings |
| from .utils.logger import get_logger |
|
|
| logger = get_logger(__name__) |
| settings = get_settings() |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| """Application lifespan events.""" |
| logger.info(f"Starting {settings.app_name}") |
| yield |
| logger.info(f"Shutting down {settings.app_name}") |
|
|
|
|
| app = FastAPI( |
| title=settings.app_name, |
| debug=settings.debug, |
| lifespan=lifespan, |
| ) |
|
|
|
|
| @app.get("/health") |
| async def health_check() -> dict[str, str]: |
| """Health check endpoint.""" |
| return {"status": "ok", "app": settings.app_name} |
| EOF |
|
|
| |
| cat > tests/conftest.py << 'EOF' |
| """pytest fixtures shared across all tests.""" |
|
|
| import pytest |
| from fastapi.testclient import TestClient |
|
|
| from src.main import app |
|
|
|
|
| @pytest.fixture |
| def client() -> TestClient: |
| """Return a FastAPI test client.""" |
| return TestClient(app) |
|
|
|
|
| @pytest.fixture |
| def sample_user() -> dict: |
| """Return sample user data.""" |
| return { |
| "email": "test@example.com", |
| "password": "securepassword123", |
| } |
| EOF |
|
|
| |
| cat > tests/unit/test_main.py << 'EOF' |
| """Tests for main application.""" |
|
|
| from fastapi.testclient import TestClient |
|
|
| from src.main import app |
|
|
|
|
| class TestHealthCheck: |
| """Test health check endpoint.""" |
|
|
| def test_health_returns_ok(self, client: TestClient) -> None: |
| """Health endpoint should return status ok.""" |
| response = client.get("/health") |
| assert response.status_code == 200 |
| data = response.json() |
| assert data["status"] == "ok" |
| EOF |
|
|
| echo "✓ Starter files created" |
|
|
| |
| echo "" |
| echo "✅ Setup complete!" |
| echo "" |
| echo "Next steps:" |
| echo " 1. Edit .env with your configuration" |
| echo " 2. Edit docs/PRD.md with your project requirements" |
| echo " 3. Start Ollama: ./scripts/start-ollama.sh" |
| echo " 4. Activate venv: source .venv/bin/activate" |
| echo " 5. Start coding with AI assistance!" |
| echo "" |
| echo "Agent commands:" |
| echo " python agents/smolagent_runner.py 'your task here'" |
| echo " python agents/multiagent_workflow.py 'your project here'" |
|
|