diff --git a/codegen-examples/examples/Deployer.sh b/codegen-examples/examples/Deployer.sh index f93615762..f2fed5dfc 100755 --- a/codegen-examples/examples/Deployer.sh +++ b/codegen-examples/examples/Deployer.sh @@ -7,21 +7,52 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Print banner +echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Codegen Modal Deployer ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" +echo "" + # Check if Python is installed if ! command -v python3 &> /dev/null; then - echo "Python 3 is required but not installed. Please install Python 3 and try again." + echo -e "${RED}Python 3 is required but not installed. Please install Python 3 and try again.${NC}" exit 1 fi +# Check Python version +PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +PYTHON_VERSION_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1) +PYTHON_VERSION_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2) + +if [ "$PYTHON_VERSION_MAJOR" -lt 3 ] || ([ "$PYTHON_VERSION_MAJOR" -eq 3 ] && [ "$PYTHON_VERSION_MINOR" -lt 9 ]); then + echo -e "${YELLOW}Warning: Python 3.9+ is recommended. You are using Python $PYTHON_VERSION.${NC}" + read -p "Continue anyway? (y/n): " continue_anyway + if [ "$continue_anyway" != "y" ] && [ "$continue_anyway" != "Y" ]; then + echo "Exiting." + exit 0 + fi +fi + # Check if Modal is installed if ! python3 -c "import modal" &> /dev/null; then - echo "Modal is not installed. Installing now..." - pip install modal + echo -e "${YELLOW}Modal is not installed. Installing now...${NC}" + pip install modal==1.0.0 fi +# Check Modal version +MODAL_VERSION=$(python3 -c "import modal; print(modal.__version__)") +echo -e "${GREEN}Using Modal version: $MODAL_VERSION${NC}" + # Check if Modal token is set up if ! modal token list &> /dev/null; then - echo "Modal token not set up. Please run 'modal token new' to set up your Modal token." + echo -e "${RED}Modal token not set up. Please run 'modal token new' to set up your Modal token.${NC}" exit 1 fi @@ -31,11 +62,32 @@ deploy_example() { local example_name=$(basename "$example_dir") if [ -f "$example_dir/deploy.sh" ]; then - echo "Deploying $example_name..." + echo -e "${BLUE}Deploying $example_name...${NC}" (cd "$example_dir" && bash deploy.sh) - return $? + local status=$? + if [ $status -eq 0 ]; then + echo -e "${GREEN}✓ $example_name deployed successfully.${NC}" + else + echo -e "${RED}✗ $example_name deployment failed with status $status.${NC}" + fi + return $status else - echo "No deploy.sh script found for $example_name. Skipping." + echo -e "${YELLOW}No deploy.sh script found for $example_name. Skipping.${NC}" + return 1 + fi +} + +# Function to verify deployment +verify_deployment() { + local example_name="$1" + local app_name="$2" + + echo -e "${BLUE}Verifying deployment of $example_name...${NC}" + if modal app status "$app_name" | grep -q "RUNNING"; then + echo -e "${GREEN}✓ $example_name is running.${NC}" + return 0 + else + echo -e "${YELLOW}! $example_name is not running. It may still be starting up or may have failed to deploy.${NC}" return 1 fi } @@ -44,26 +96,30 @@ deploy_example() { examples=() for dir in "$SCRIPT_DIR"/*/; do if [ -f "${dir}deploy.sh" ]; then - examples+=("$(basename "$dir")") + examples+=($(basename "$dir")) fi done if [ ${#examples[@]} -eq 0 ]; then - echo "No deployable examples found." + echo -e "${RED}No deployable examples found.${NC}" exit 1 fi +# Sort examples alphabetically +IFS=$'\n' examples=($(sort <<<"${examples[*]}")) +unset IFS + # Display menu -echo "Available examples for deployment:" +echo -e "${GREEN}Available examples for deployment:${NC}" echo "" for i in "${!examples[@]}"; do - echo "[$((i+1))] ${examples[$i]}" + echo -e "${BLUE}[$((i+1))] ${examples[$i]}${NC}" done echo "" -echo "[a] Deploy all examples" -echo "[q] Quit" +echo -e "${BLUE}[a] Deploy all examples${NC}" +echo -e "${BLUE}[q] Quit${NC}" echo "" # Get user selection @@ -72,7 +128,7 @@ while true; do read -p "Select examples to deploy (e.g., '1 3 5' or 'a' for all, 'q' to quit, 'd' when done): " selection if [ "$selection" == "q" ]; then - echo "Exiting without deployment." + echo -e "${YELLOW}Exiting without deployment.${NC}" exit 0 elif [ "$selection" == "a" ]; then for i in "${!examples[@]}"; do @@ -81,7 +137,7 @@ while true; do break elif [ "$selection" == "d" ]; then if [ ${#selected_indices[@]} -eq 0 ]; then - echo "No examples selected. Please select at least one example." + echo -e "${YELLOW}No examples selected. Please select at least one example.${NC}" else break fi @@ -93,12 +149,12 @@ while true; do # Check if already selected if [[ ! " ${selected_indices[@]} " =~ " ${idx} " ]]; then selected_indices+=($idx) - echo "Added ${examples[$idx]} to deployment list." + echo -e "${GREEN}Added ${examples[$idx]} to deployment list.${NC}" else - echo "${examples[$idx]} is already selected." + echo -e "${YELLOW}${examples[$idx]} is already selected.${NC}" fi else - echo "Invalid selection: $num. Please enter numbers between 1 and ${#examples[@]}." + echo -e "${RED}Invalid selection: $num. Please enter numbers between 1 and ${#examples[@]}.${NC}" fi done fi @@ -106,21 +162,21 @@ done # Show selected examples echo "" -echo "Selected examples for deployment:" +echo -e "${GREEN}Selected examples for deployment:${NC}" for idx in "${selected_indices[@]}"; do - echo "- ${examples[$idx]}" + echo -e "${BLUE}- ${examples[$idx]}${NC}" done echo "" # Confirm deployment read -p "Deploy these examples? (y/n): " confirm if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then - echo "Deployment cancelled." + echo -e "${YELLOW}Deployment cancelled.${NC}" exit 0 fi # Deploy selected examples concurrently -echo "Starting deployment of selected examples..." +echo -e "${GREEN}Starting deployment of selected examples...${NC}" pids=() results=() @@ -142,8 +198,8 @@ done # Print summary echo "" -echo "Deployment Summary:" -echo "==================" +echo -e "${BLUE}Deployment Summary:${NC}" +echo -e "${BLUE}==================${NC}" success_count=0 failure_count=0 @@ -153,23 +209,79 @@ for i in "${!selected_indices[@]}"; do result="${results[$i]}" if [ "$result" -eq 0 ]; then - echo "✅ ${example}: SUCCESS" + echo -e "${GREEN}✓ ${example}: SUCCESS${NC}" ((success_count++)) else - echo "❌ ${example}: FAILED" + echo -e "${RED}✗ ${example}: FAILED${NC}" ((failure_count++)) fi done echo "" -echo "Total: $((success_count + failure_count)), Successful: $success_count, Failed: $failure_count" +echo -e "${BLUE}Total: $((success_count + failure_count)), Successful: $success_count, Failed: $failure_count${NC}" if [ "$failure_count" -gt 0 ]; then echo "" - echo "Some deployments failed. Check the logs above for details." + echo -e "${RED}Some deployments failed. Check the logs above for details.${NC}" exit 1 fi echo "" -echo "All deployments completed successfully!" +echo -e "${GREEN}All deployments completed successfully!${NC}" + +# Offer to view logs +echo "" +echo -e "${BLUE}Options:${NC}" +echo -e "${BLUE}[l] View logs for a deployed example${NC}" +echo -e "${BLUE}[s] View status of all deployed examples${NC}" +echo -e "${BLUE}[q] Quit${NC}" +echo "" + +read -p "Select an option: " option +if [ "$option" == "l" ]; then + echo "" + echo -e "${BLUE}Select an example to view logs:${NC}" + for i in "${!selected_indices[@]}"; do + idx="${selected_indices[$i]}" + echo -e "${BLUE}[$((i+1))] ${examples[$idx]}${NC}" + done + echo "" + + read -p "Enter number: " log_selection + if [[ "$log_selection" =~ ^[0-9]+$ ]] && [ "$log_selection" -ge 1 ] && [ "$log_selection" -le ${#selected_indices[@]} ]; then + log_idx=$((log_selection-1)) + selected_idx="${selected_indices[$log_idx]}" + example="${examples[$selected_idx]}" + + # Extract app name from deploy.sh + app_name=$(grep -o "modal app [a-zA-Z0-9_-]*" "$SCRIPT_DIR/$example/deploy.sh" | head -1 | awk '{print $3}') + if [ -z "$app_name" ]; then + # Try to guess app name from example name + app_name=$(echo "$example" | tr '_' '-') + fi + + echo -e "${BLUE}Viewing logs for $example (app: $app_name)...${NC}" + modal app logs "$app_name" + else + echo -e "${RED}Invalid selection.${NC}" + fi +elif [ "$option" == "s" ]; then + echo "" + echo -e "${BLUE}Status of deployed examples:${NC}" + for i in "${!selected_indices[@]}"; do + idx="${selected_indices[$i]}" + example="${examples[$idx]}" + + # Extract app name from deploy.sh + app_name=$(grep -o "modal app [a-zA-Z0-9_-]*" "$SCRIPT_DIR/$example/deploy.sh" | head -1 | awk '{print $3}') + if [ -z "$app_name" ]; then + # Try to guess app name from example name + app_name=$(echo "$example" | tr '_' '-') + fi + + echo -e "${BLUE}$example (app: $app_name):${NC}" + modal app status "$app_name" | grep -E "RUNNING|STOPPED|FAILED" + done +fi +echo -e "${GREEN}Thank you for using the Codegen Modal Deployer!${NC}" diff --git a/codegen-examples/examples/README.md b/codegen-examples/examples/README.md new file mode 100644 index 000000000..29f9bff2a --- /dev/null +++ b/codegen-examples/examples/README.md @@ -0,0 +1,78 @@ +# Codegen Examples + +This directory contains a collection of examples demonstrating various use cases of the [Codegen](https://codegen.com) SDK. + +## Types of Examples + +The examples in this directory fall into two main categories: + +1. **Code Transformation Examples**: One-time utilities that transform code in various ways (e.g., migrating from one library to another, converting code patterns, etc.) + +2. **Modal-based Service Examples**: Applications that can be deployed as services using [Modal](https://modal.com), such as chatbots, webhooks handlers, and analytics tools. + +## Using the Modal Deployer + +For Modal-based examples, we provide a convenient deployment tool called `Deployer.sh` that allows you to interactively select and deploy multiple examples concurrently. + +### Prerequisites + +- Python 3.9 or higher +- [Modal](https://modal.com/) account and CLI +- Git + +### Running the Deployer + +To use the deployer: + +```bash +# Navigate to the examples directory +cd examples + +# Run the deployer script +bash Deployer.sh +``` + +The deployer will: + +1. Check for required dependencies (Python, Modal) +2. Display a list of deployable examples +3. Allow you to select which examples to deploy +4. Deploy the selected examples concurrently +5. Provide a summary of deployment results +6. Offer options to view logs or status of deployed examples + +### Available Modal Examples + +The following examples can be deployed using the Deployer.sh script: + +- `ai_impact_analysis`: Analyze the impact of AI on codebases +- `codegen-mcp-server`: MCP server implementation +- `codegen_app`: Codegen web application +- `cyclomatic_complexity`: Calculate cyclomatic complexity of code +- `deep_code_research`: Deep research on code repositories +- `delete_dead_code`: Identify and remove dead code +- `document_functions`: Automatically document functions +- `github_checks`: GitHub checks integration +- `linear_webhooks`: Linear webhooks handler +- `modal_repo_analytics`: Repository analytics using Modal +- `pr_review_bot`: PR review automation +- `repo_analytics`: Repository analytics tools +- `slack_chatbot`: Slack chatbot integration +- `snapshot_event_handler`: Event handler for snapshots +- `swebench_agent_run`: SWE benchmark agent +- `ticket-to-pr`: Convert tickets to PRs + +Each of these examples has its own `deploy.sh` script and README with specific deployment instructions. + +## Running Non-Modal Examples + +For examples that don't have a `deploy.sh` script, you can run them locally following the instructions in their respective README files. These examples typically perform one-time code transformations and don't need to be deployed as services. + +## Contributing + +If you'd like to add a new example, please follow the [Contributing Guide](../CONTRIBUTING.md) for instructions. + +## License + +All examples are licensed under the [Apache 2.0 license](../LICENSE). + diff --git a/codegen-examples/examples/deploy.sh.template b/codegen-examples/examples/deploy.sh.template new file mode 100644 index 000000000..949a68cfa --- /dev/null +++ b/codegen-examples/examples/deploy.sh.template @@ -0,0 +1,97 @@ +#!/bin/bash + +# Exit on error +set -e + +# Get the directory of the script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Replace with your app name +APP_NAME="your-app-name" + +echo -e "${BLUE}Deploying $APP_NAME to Modal...${NC}" + +# Check if Python is installed +if ! command -v python3 &> /dev/null; then + echo -e "${RED}Python 3 is required but not installed. Please install Python 3 and try again.${NC}" + exit 1 +fi + +# Check if Modal is installed +if ! python3 -c "import modal" &> /dev/null; then + echo -e "${YELLOW}Modal is not installed. Installing now...${NC}" + pip install modal==1.0.0 +fi + +# Check Modal version +MODAL_VERSION=$(python3 -c "import modal; print(modal.__version__)") +echo -e "${GREEN}Using Modal version: $MODAL_VERSION${NC}" + +# Check if Modal token is set up +if ! modal token list &> /dev/null; then + echo -e "${RED}Modal token not set up. Please run 'modal token new' to set up your Modal token.${NC}" + exit 1 +fi + +# Check if .env file exists (if needed) +if [ ! -f .env ]; then + if [ -f .env.template ]; then + echo -e "${YELLOW}.env file not found. Creating from template...${NC}" + cp .env.template .env + echo -e "${RED}Please edit the .env file with your credentials before deploying.${NC}" + exit 1 + else + echo -e "${YELLOW}No .env.template file found. Creating a basic .env template...${NC}" + cat > .env.template << EOL +# Add your environment variables here +# API_KEY=your_api_key +# OTHER_SECRET=your_secret + +# Modal configuration (optional) +MODAL_API_KEY=your_modal_api_key +EOL + cp .env.template .env + echo -e "${RED}Please edit the .env file with your credentials before deploying.${NC}" + exit 1 + fi +fi + +# Check for required environment variables (if needed) +# if ! grep -q "REQUIRED_ENV_VAR" .env; then +# echo -e "${RED}Missing required environment variables in .env file.${NC}" +# echo -e "${YELLOW}Please ensure REQUIRED_ENV_VAR is set.${NC}" +# exit 1 +# fi + +# Install dependencies (if needed) +# echo -e "${BLUE}Installing dependencies...${NC}" +# pip install -r requirements.txt + +# Deploy the application +echo -e "${BLUE}Deploying $APP_NAME to Modal...${NC}" +python3 app.py # Replace with your main Python file + +# Verify deployment +if modal app status $APP_NAME | grep -q "RUNNING"; then + echo -e "${GREEN}Deployment complete! $APP_NAME is now running.${NC}" +else + echo -e "${YELLOW}Deployment completed, but the app may not be running yet.${NC}" +fi + +echo -e "${BLUE}You can check the status with 'modal app status $APP_NAME'${NC}" +echo -e "${BLUE}To view logs, run 'modal app logs $APP_NAME'${NC}" + +# Get the app URL (if applicable) +echo -e "${GREEN}Your app URL is:${NC}" +modal app show $APP_NAME | grep -o "https://.*$APP_NAME.*" || echo -e "${YELLOW}URL not found. Run 'modal app show $APP_NAME' to get your app URL.${NC}" + +echo -e "${BLUE}Deployment process completed.${NC}" + diff --git a/codegen-examples/examples/linear_webhooks/.env.template b/codegen-examples/examples/linear_webhooks/.env.template index a68a3afa9..a7b5f41dc 100644 --- a/codegen-examples/examples/linear_webhooks/.env.template +++ b/codegen-examples/examples/linear_webhooks/.env.template @@ -1,7 +1,8 @@ -# Linear API credentials -LINEAR_ACCESS_TOKEN="your_linear_api_token" -LINEAR_SIGNING_SECRET="your_linear_webhook_signing_secret" -LINEAR_TEAM_ID="your_linear_team_id" +# Linear credentials +LINEAR_ACCESS_TOKEN=your_linear_api_token +LINEAR_SIGNING_SECRET=your_linear_webhook_signing_secret +LINEAR_TEAM_ID=your_linear_team_id # Modal configuration (optional) -MODAL_API_KEY="your_modal_api_key" +MODAL_API_KEY=your_modal_api_key + diff --git a/codegen-examples/examples/linear_webhooks/deploy.sh b/codegen-examples/examples/linear_webhooks/deploy.sh index 4bf90c6c3..dfaae2927 100755 --- a/codegen-examples/examples/linear_webhooks/deploy.sh +++ b/codegen-examples/examples/linear_webhooks/deploy.sh @@ -7,41 +7,73 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Deploying Linear Webhooks to Modal...${NC}" + # Check if Python is installed if ! command -v python3 &> /dev/null; then - echo "Python 3 is required but not installed. Please install Python 3 and try again." + echo -e "${RED}Python 3 is required but not installed. Please install Python 3 and try again.${NC}" exit 1 fi # Check if Modal is installed if ! python3 -c "import modal" &> /dev/null; then - echo "Modal is not installed. Installing now..." - pip install modal + echo -e "${YELLOW}Modal is not installed. Installing now...${NC}" + pip install modal==1.0.0 fi +# Check Modal version +MODAL_VERSION=$(python3 -c "import modal; print(modal.__version__)") +echo -e "${GREEN}Using Modal version: $MODAL_VERSION${NC}" + # Check if Modal token is set up if ! modal token list &> /dev/null; then - echo "Modal token not set up. Please run 'modal token new' to set up your Modal token." + echo -e "${RED}Modal token not set up. Please run 'modal token new' to set up your Modal token.${NC}" exit 1 fi # Check if .env file exists if [ ! -f .env ]; then if [ -f .env.template ]; then - echo ".env file not found. Creating from template..." + echo -e "${YELLOW}.env file not found. Creating from template...${NC}" cp .env.template .env - echo "Please edit the .env file with your credentials before deploying." + echo -e "${RED}Please edit the .env file with your credentials before deploying.${NC}" exit 1 else - echo "Neither .env nor .env.template file found. Please create a .env file with your credentials." + echo -e "${RED}Neither .env nor .env.template file found. Please create a .env file with your credentials.${NC}" exit 1 fi fi +# Check for required environment variables +if ! grep -q "LINEAR_ACCESS_TOKEN" .env || ! grep -q "LINEAR_SIGNING_SECRET" .env; then + echo -e "${RED}Missing required environment variables in .env file.${NC}" + echo -e "${YELLOW}Please ensure LINEAR_ACCESS_TOKEN and LINEAR_SIGNING_SECRET are set.${NC}" + exit 1 +fi + # Deploy the application -echo "Deploying Linear Webhooks to Modal..." +echo -e "${BLUE}Deploying Linear Webhooks to Modal...${NC}" python3 webhooks.py -echo "Deployment complete! You can check the status with 'modal app status linear-webhooks'" -echo "To view logs, run 'modal app logs linear-webhooks'" +# Verify deployment +if modal app status linear-webhooks | grep -q "RUNNING"; then + echo -e "${GREEN}Deployment complete! Linear Webhooks is now running.${NC}" +else + echo -e "${YELLOW}Deployment completed, but the app may not be running yet.${NC}" +fi + +echo -e "${BLUE}You can check the status with 'modal app status linear-webhooks'${NC}" +echo -e "${BLUE}To view logs, run 'modal app logs linear-webhooks'${NC}" + +# Get the webhook URL +echo -e "${GREEN}Your Linear webhook URL is:${NC}" +modal app show linear-webhooks | grep -o "https://.*linear-webhooks.*" || echo -e "${YELLOW}URL not found. Run 'modal app show linear-webhooks' to get your webhook URL.${NC}" +echo -e "${BLUE}Remember to configure this URL in your Linear workspace settings.${NC}" diff --git a/codegen-examples/examples/linear_webhooks/webhooks.py b/codegen-examples/examples/linear_webhooks/webhooks.py index ba496e2ba..c5dde8b21 100644 --- a/codegen-examples/examples/linear_webhooks/webhooks.py +++ b/codegen-examples/examples/linear_webhooks/webhooks.py @@ -1,106 +1,161 @@ +"""Linear webhooks handler using Modal and Codegen.""" + +import hashlib +import hmac +import json import os +from datetime import datetime +from typing import Any, Dict, List, Optional, Union + import modal -from codegen.extensions.events.app import CodegenApp -from codegen.extensions.linear.types import LinearEvent, LinearIssue, LinearComment, LinearUser -from codegen.shared.logging.get_logger import get_logger - -logger = get_logger(__name__) - -# Create a Modal image with the necessary dependencies -image = modal.Image.debian_slim(python_version="3.13").apt_install("git").pip_install("fastapi[standard]", "codegen>=v0.26.3") - -# Initialize the CodegenApp with a name and the image -app = CodegenApp(name="linear-webhooks", modal_api_key="", image=image) - -# Define a Modal class to handle Linear events -@app.cls(secrets=[modal.Secret.from_dotenv()], keep_warm=1) -class LinearEventHandlers: - @modal.enter() - def enter(self): - """Subscribe to all Linear webhook handlers when the app starts""" - logger.info("Subscribing to Linear webhook handlers") - app.linear.subscribe_all_handlers() - - @modal.exit() - def exit(self): - """Unsubscribe from all Linear webhook handlers when the app stops""" - logger.info("Unsubscribing from Linear webhook handlers") - app.linear.unsubscribe_all_handlers() - - @modal.web_endpoint(method="POST") - @app.linear.event("Issue") - def handle_issue(self, event: LinearEvent): - """Handle Linear Issue events - - This endpoint will be triggered when an issue is created, updated, or deleted in Linear. - """ - # Check if the data is an Issue before accessing title - if isinstance(event.data, LinearIssue): - issue_title = event.data.title - logger.info(f"Received Linear Issue event: {event.action} - {issue_title}") - return { - "status": "success", - "message": f"Processed Linear Issue event: {event.action}", - "issue_id": event.data.id, - "issue_title": issue_title - } - else: - logger.warning(f"Received non-Issue data for Issue event: {event.action}") - return { - "status": "warning", - "message": f"Received non-Issue data for Issue event: {event.action}", - "id": event.data.id - } - - @modal.web_endpoint(method="POST") - @app.linear.event("Comment") - def handle_comment(self, event: LinearEvent): - """Handle Linear Comment events - - This endpoint will be triggered when a comment is created, updated, or deleted in Linear. - """ - # Check if the data is a Comment before processing - if isinstance(event.data, LinearComment): - logger.info(f"Received Linear Comment event: {event.action}") - - # Get the comment body and user information if available - comment_body = event.data.body - user_info = "" - if event.data.user: - user_info = f" by {event.data.user.name}" - - logger.info(f"Comment{user_info}: {comment_body}") - - return { - "status": "success", - "message": f"Processed Linear Comment event: {event.action}", - "comment_id": event.data.id, - "comment_body": comment_body - } +from fastapi import FastAPI, Header, HTTPException, Request +from pydantic import BaseModel, Field + +# Create image with dependencies +image = ( + modal.Image.debian_slim(python_version="3.13") + .pip_install( + "codegen>=0.6.1", + "python-dotenv>=1.0.0", + ) +) + +# Create Modal app +app = modal.App("linear-webhooks") + +# Create a volume for persistent storage +volume = modal.Volume.from_name("linear-data", create_if_missing=True) + + +class LinearEvent(BaseModel): + """Linear webhook event model.""" + + action: str + type: str + data: Dict[str, Any] + url: Optional[str] = None + updatedFrom: Optional[Dict[str, Any]] = None + createdAt: datetime = Field(default_factory=datetime.now) + + +@app.function( + image=image, + secrets=[modal.Secret.from_dotenv()], + volumes={"/data": volume}, +) +@modal.asgi_app() +def fastapi_app(): + """Create FastAPI app with Linear webhook handlers.""" + web_app = FastAPI(title="Linear Webhooks Handler") + + @web_app.get("/") + async def root(): + """Root endpoint for health checks.""" + return {"status": "ok", "message": "Linear webhooks handler is running"} + + @web_app.post("/webhook") + async def webhook( + request: Request, + x_linear_delivery: Optional[str] = Header(None), + x_linear_signature: Optional[str] = Header(None), + ): + """Handle Linear webhook events.""" + # Verify signature if provided + if x_linear_signature: + signing_secret = os.environ.get("LINEAR_SIGNING_SECRET") + if not signing_secret: + raise HTTPException(status_code=500, detail="LINEAR_SIGNING_SECRET not configured") + + body = await request.body() + signature = hmac.new( + signing_secret.encode("utf-8"), + body, + hashlib.sha256, + ).hexdigest() + + if not hmac.compare_digest(signature, x_linear_signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + # Parse event data + try: + data = await request.json() + event = LinearEvent(**data) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Invalid event data: {str(e)}") + + # Log event + log_event(event) + + # Handle different event types + if event.type == "Issue": + return handle_issue_event(event) + elif event.type == "Comment": + return handle_comment_event(event) else: - logger.warning(f"Received non-Comment data for Comment event: {event.action}") - return { - "status": "warning", - "message": f"Received non-Comment data for Comment event: {event.action}", - "id": event.data.id - } - - @modal.web_endpoint(method="POST") - @app.linear.event("*") - def handle_generic(self, event: LinearEvent): - """Handle any other Linear events - - This endpoint will be triggered for any Linear event type not explicitly handled. - """ - logger.info(f"Received Linear event: {event.type} - {event.action}") + return {"status": "ignored", "message": f"Event type {event.type} not handled"} + + return web_app + + +def log_event(event: LinearEvent): + """Log Linear event to file.""" + log_file = f"/data/linear_events_{datetime.now().strftime('%Y%m%d')}.jsonl" + with open(log_file, "a") as f: + f.write(json.dumps(event.dict()) + "\n") + print(f"Logged {event.type} {event.action} event") + + +def handle_issue_event(event: LinearEvent) -> Dict[str, str]: + """Handle Linear issue events.""" + action = event.action + issue_data = event.data + issue_id = issue_data.get("id") + issue_title = issue_data.get("title") + + print(f"Handling issue event: {action} - {issue_title} ({issue_id})") + + if action == "create": + # Handle issue creation + return {"status": "success", "message": f"Processed issue creation: {issue_title}"} + elif action == "update": + # Handle issue update + updated_fields = event.updatedFrom or {} return { "status": "success", - "message": f"Processed Linear event: {event.type} - {event.action}", - "event_type": event.type, - "event_action": event.action + "message": f"Processed issue update: {issue_title}", + "updated_fields": list(updated_fields.keys()), } + elif action == "remove": + # Handle issue deletion + return {"status": "success", "message": f"Processed issue deletion: {issue_id}"} + else: + return {"status": "ignored", "message": f"Issue action {action} not handled"} -# If running this file directly, this will deploy the app to Modal -if __name__ == "__main__": - app.serve() +def handle_comment_event(event: LinearEvent) -> Dict[str, str]: + """Handle Linear comment events.""" + action = event.action + comment_data = event.data + comment_id = comment_data.get("id") + comment_body = comment_data.get("body") + + print(f"Handling comment event: {action} - {comment_id}") + + if action == "create": + # Handle comment creation + return {"status": "success", "message": "Processed comment creation"} + elif action == "update": + # Handle comment update + return {"status": "success", "message": "Processed comment update"} + elif action == "remove": + # Handle comment deletion + return {"status": "success", "message": "Processed comment deletion"} + else: + return {"status": "ignored", "message": f"Comment action {action} not handled"} + + +if __name__ == "__main__": + # When running directly, deploy the app + print("Deploying linear-webhooks to Modal...") + modal.serve.deploy(fastapi_app) + print("Deployment complete! Check status with 'modal app status linear-webhooks'") diff --git a/codegen-examples/examples/slack_chatbot/api.py b/codegen-examples/examples/slack_chatbot/api.py index d6cf49829..4770b5174 100644 --- a/codegen-examples/examples/slack_chatbot/api.py +++ b/codegen-examples/examples/slack_chatbot/api.py @@ -51,7 +51,7 @@ def answer_question(query: str) -> tuple[str, list[tuple[str, int]]]: if "#chunk" in filepath: filepath = filepath.split("#chunk")[0] file = codebase.get_file(filepath) - context += f"File: {file.filepath}\n```\n{file.content}\n```\n\n" + context += f"File: {file.filepath}\n```\n{file.content}```\n\n" # Create prompt for OpenAI prompt = f"""You are an expert on FastAPI. Given the following code context and question, provide a clear and accurate answer. @@ -95,13 +95,17 @@ def answer_question(query: str) -> tuple[str, list[tuple[str, int]]]: ) # Create Modal app -app = modal.App("codegen-slack-demo") +app = modal.App("slack-chatbot") + +# Create a volume to store the vector index +volume = modal.Volume.from_name("codegen-indices", create_if_missing=True) @app.function( image=image, secrets=[modal.Secret.from_dotenv()], timeout=3600, + volumes={"/root": volume}, ) @modal.asgi_app() def fastapi_app(): @@ -170,3 +174,10 @@ async def verify(request: Request): return await handler.handle(request) return web_app + + +if __name__ == "__main__": + # When running directly, deploy the app + print("Deploying slack-chatbot to Modal...") + modal.serve.deploy(fastapi_app) + print("Deployment complete! Check status with 'modal app status slack-chatbot'") diff --git a/codegen-examples/examples/slack_chatbot/deploy.sh b/codegen-examples/examples/slack_chatbot/deploy.sh index c6567ec7d..32b97f84b 100755 --- a/codegen-examples/examples/slack_chatbot/deploy.sh +++ b/codegen-examples/examples/slack_chatbot/deploy.sh @@ -7,51 +7,86 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Deploying Slack Chatbot to Modal...${NC}" + # Check if Python is installed if ! command -v python3 &> /dev/null; then - echo "Python 3 is required but not installed. Please install Python 3 and try again." + echo -e "${RED}Python 3 is required but not installed. Please install Python 3 and try again.${NC}" exit 1 fi # Check if Modal is installed if ! python3 -c "import modal" &> /dev/null; then - echo "Modal is not installed. Installing now..." - pip install modal + echo -e "${YELLOW}Modal is not installed. Installing now...${NC}" + pip install modal==1.0.0 fi +# Check Modal version +MODAL_VERSION=$(python3 -c "import modal; print(modal.__version__)") +echo -e "${GREEN}Using Modal version: $MODAL_VERSION${NC}" + # Check if Modal token is set up if ! modal token list &> /dev/null; then - echo "Modal token not set up. Please run 'modal token new' to set up your Modal token." + echo -e "${RED}Modal token not set up. Please run 'modal token new' to set up your Modal token.${NC}" exit 1 fi # Check if .env file exists if [ ! -f .env ]; then if [ -f .env.template ]; then - echo ".env file not found. Creating from template..." + echo -e "${YELLOW}.env file not found. Creating from template...${NC}" cp .env.template .env - echo "Please edit the .env file with your credentials before deploying." + echo -e "${RED}Please edit the .env file with your credentials before deploying.${NC}" exit 1 else - echo "No .env.template file found. Creating a basic .env template..." + echo -e "${YELLOW}No .env.template file found. Creating a basic .env template...${NC}" cat > .env.template << EOL # Slack credentials SLACK_BOT_TOKEN=your_slack_bot_token SLACK_SIGNING_SECRET=your_slack_signing_secret +# OpenAI API key +OPENAI_API_KEY=your_openai_api_key + # Modal configuration (optional) MODAL_API_KEY=your_modal_api_key EOL cp .env.template .env - echo "Please edit the .env file with your credentials before deploying." + echo -e "${RED}Please edit the .env file with your credentials before deploying.${NC}" exit 1 fi fi +# Check for required environment variables +if ! grep -q "SLACK_BOT_TOKEN" .env || ! grep -q "SLACK_SIGNING_SECRET" .env || ! grep -q "OPENAI_API_KEY" .env; then + echo -e "${RED}Missing required environment variables in .env file.${NC}" + echo -e "${YELLOW}Please ensure SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, and OPENAI_API_KEY are set.${NC}" + exit 1 +fi + # Deploy the application -echo "Deploying Slack Chatbot to Modal..." +echo -e "${BLUE}Deploying Slack Chatbot to Modal...${NC}" python3 api.py -echo "Deployment complete! You can check the status with 'modal app status slack-chatbot'" -echo "To view logs, run 'modal app logs slack-chatbot'" +# Verify deployment +if modal app status slack-chatbot | grep -q "RUNNING"; then + echo -e "${GREEN}Deployment complete! Slack Chatbot is now running.${NC}" +else + echo -e "${YELLOW}Deployment completed, but the app may not be running yet.${NC}" +fi + +echo -e "${BLUE}You can check the status with 'modal app status slack-chatbot'${NC}" +echo -e "${BLUE}To view logs, run 'modal app logs slack-chatbot'${NC}" + +# Get the webhook URL +echo -e "${GREEN}Your Slack webhook URL is:${NC}" +modal app show slack-chatbot | grep -o "https://.*slack-chatbot.*" || echo -e "${YELLOW}URL not found. Run 'modal app show slack-chatbot' to get your webhook URL.${NC}" +echo -e "${BLUE}Remember to configure this URL in your Slack app settings.${NC}"