From 03d8b145842058723d17a2d9043555912a7ec74d Mon Sep 17 00:00:00 2001 From: Ebony Louis Date: Mon, 8 Dec 2025 10:03:06 -0500 Subject: [PATCH 1/2] donation prep: cleanup recipe key flow --- .github/ISSUE_TEMPLATE/submit-recipe.yml | 11 - .github/scripts/send_key.py | 229 -------------------- .github/workflows/send-api-key.yml | 71 ------ .github/workflows/validate-recipe-pr.yml | 265 ----------------------- 4 files changed, 576 deletions(-) delete mode 100644 .github/scripts/send_key.py delete mode 100644 .github/workflows/send-api-key.yml delete mode 100644 .github/workflows/validate-recipe-pr.yml diff --git a/.github/ISSUE_TEMPLATE/submit-recipe.yml b/.github/ISSUE_TEMPLATE/submit-recipe.yml index c9b6b0d5aa0c..b2af2ae9e5f4 100644 --- a/.github/ISSUE_TEMPLATE/submit-recipe.yml +++ b/.github/ISSUE_TEMPLATE/submit-recipe.yml @@ -16,9 +16,7 @@ body: ๐Ÿช„ **What Happens After?** - If accepted, we'll publish your recipe to the [goose recipes cookbook](https://block.github.io/goose/recipes) - - You'll receive OpenRouter **LLM API credits** as a thank you! - Your GitHub handle will be displayed and linked on the recipe card - - If you provide an email below, we'll email you your credits when your recipe is approved and merged. - If the YAML has any issues, goose will comment with validation errors so you can fix and resubmit. ๐Ÿงช **Pro Tip:** You can test your recipe locally in your terminal with: @@ -86,15 +84,6 @@ body: validations: required: true - - type: input - id: email - attributes: - label: Your Email (optional) - description: If your recipe is approved, we'll email your LLM API credits here. - placeholder: yourname@example.com - validations: - required: false - - type: markdown attributes: value: | diff --git a/.github/scripts/send_key.py b/.github/scripts/send_key.py deleted file mode 100644 index 554873eadd4d..000000000000 --- a/.github/scripts/send_key.py +++ /dev/null @@ -1,229 +0,0 @@ -import os -import requests -import re -import email_validator -from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Mail -from python_http_client.exceptions import HTTPError - - -def main(): - # Environment variable validation - required_envs = ["GITHUB_TOKEN", "GITHUB_SHA", "GITHUB_REPOSITORY", "PROVISIONING_API_KEY", "EMAIL_API_KEY"] - missing = [env for env in required_envs if env not in os.environ] - if missing: - print(f"โŒ Missing environment variables: {', '.join(missing)}") - exit(2) - - GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] - GITHUB_SHA = os.environ["GITHUB_SHA"] - REPO_NAME = os.environ["GITHUB_REPOSITORY"] - PROVISIONING_API_KEY = os.environ["PROVISIONING_API_KEY"] - SENDGRID_API_KEY = os.environ["EMAIL_API_KEY"] - - pr_number = get_pr_number_from_sha() - pr_data = fetch_pr_body(pr_number, GITHUB_TOKEN, REPO_NAME) - pr_body = pr_data.get("body", "") - - email = extract_email(pr_body, REPO_NAME, pr_number, GITHUB_TOKEN) - print(f"๐Ÿ“ฌ Found email: {email}") - - try: - api_key = provision_api_key(PROVISIONING_API_KEY) - print("โœ… API key generated!") - - if not send_email(email, api_key, SENDGRID_API_KEY): - print("โŒ Email failed to send. Exiting without PR comment.") - exit(2) - - comment_on_pr(GITHUB_TOKEN, REPO_NAME, pr_number, email) - - except Exception as err: - print(f"โŒ An error occurred: {err}") - exit(2) - -def get_pr_number_from_sha(): - token = os.getenv("GITHUB_TOKEN") - repo = os.getenv("GITHUB_REPOSITORY") - sha = os.getenv("GITHUB_SHA") - - url = f"https://api.github.com/repos/{repo}/commits/{sha}/pulls" - headers = { - "Authorization": f"token {token}", - "Accept": "application/vnd.github.groot-preview+json" - } - - response = requests.get(url, headers=headers) - response.raise_for_status() - - pr_data = response.json() - if pr_data: - return pr_data[0]["number"] - else: - raise Exception("No PR found for this SHA") - -def fetch_pr_body(pr_number, github_token, repo_full_name): - print("๐Ÿ” Fetching PR body...") - pr_url = f"https://api.github.com/repos/{repo_full_name}/pulls/{pr_number}" - try: - pr_resp = requests.get( - pr_url, - headers={"Authorization": f"Bearer {github_token}"} - ) - pr_resp.raise_for_status() - except requests.exceptions.RequestException as e: - print("โŒ Failed to fetch PR body:", str(e)) - raise - return pr_resp.json() - -def extract_email_from_text(text): - email_match = re.search(r"\*\*Email\*\*:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text) - if email_match: - return email_match.group(1) - email_match = re.search(r"[Ee]mail:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text) - if email_match: - return email_match.group(1) - email_match = re.search(r"\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b", text) - if email_match: - return email_match.group(1) - return None - -def fetch_pr_comments(repo_full_name, pr_number, github_token): - comments_url = f"https://api.github.com/repos/{repo_full_name}/issues/{pr_number}/comments" - try: - comments_resp = requests.get( - comments_url, - headers={"Authorization": f"Bearer {github_token}"} - ) - comments_resp.raise_for_status() - return comments_resp.json() - except requests.exceptions.RequestException as e: - print(f"โš ๏ธ Failed to fetch PR comments: {e}") - return [] - -def validate_email_address(email): - try: - valid_email = email_validator.validate_email(email) - normalized_email = valid_email.email - print(f"โœ… Email validation passed: {normalized_email}") - return normalized_email - except email_validator.EmailNotValidError as e: - print(f"โŒ Email validation failed: {e}") - return None - -def extract_email(pr_body, repo_full_name, pr_number, github_token): - print("๐Ÿ” Searching for email in PR body...") - email = extract_email_from_text(pr_body) - if email: - print(f"๐Ÿ“ง Found email in PR body: {email}") - validated_email = validate_email_address(email) - if validated_email: - return validated_email - else: - print("โš ๏ธ Email in PR body is invalid, checking comments...") - - print("๐Ÿ” No valid email found in PR body, checking comments...") - comments = fetch_pr_comments(repo_full_name, pr_number, github_token) - for comment in comments: - comment_body = comment.get("body", "") - email = extract_email_from_text(comment_body) - if email: - print(f"๐Ÿ“ง Found email in comment by {comment.get('user', {}).get('login', 'unknown')}: {email}") - validated_email = validate_email_address(email) - if validated_email: - return validated_email - else: - print("โš ๏ธ Email in comment is invalid, continuing search...") - - print("โŒ No valid email found in PR body or comments. Skipping key issuance.") - exit(2) - -def provision_api_key(provisioning_api_key): - print("๐Ÿ” Creating OpenRouter key...") - try: - key_resp = requests.post( - "https://openrouter.ai/api/v1/keys", - headers={ - "Authorization": f"Bearer {provisioning_api_key}", - "Content-Type": "application/json" - }, - json={ - "name": "goose contributor", - "label": "goose-cookbook", - "limit": 10.0 - } - ) - key_resp.raise_for_status() - except requests.exceptions.RequestException as e: - print("โŒ Failed to provision API key:", str(e)) - raise - key = key_resp.json().get("key") - if not key: - print("โŒ API response did not include a key.") - exit(2) - return key - -def send_email(email, api_key, sendgrid_api_key): - print("๐Ÿ“ค Sending email via SendGrid...") - try: - sg = SendGridAPIClient(sendgrid_api_key) - from_email = "goose team " - subject = "๐ŸŽ‰ Your goose contributor API key" - html_content = f""" -

Thank you for contributing to the goose recipe cookbook!

-

๐ŸŽ‰ Here's your $10 OpenRouter API key:

-
{api_key}
-

To use this in goose (CLI or Desktop):

- -

๐Ÿ“š Full setup instructions:
- - https://block.github.io/goose/docs/getting-started/providers/#configure-provider

-

Happy coding!
โ€“ the goose team

- """ - message = Mail( - from_email=from_email, - to_emails=email, - subject=subject, - html_content=html_content - ) - response = sg.send(message) - print(f"โœ… Email sent successfully! Status code: {response.status_code}") - if response.status_code >= 300: - print(f"โš ๏ธ Warning: Unexpected status code {response.status_code}") - print(f"Response body: {response.body}") - return False - return True - - except HTTPError as e: - print(f"โŒ SendGrid HTTP error {e.status_code}: {e.body}") - return False - except Exception as e: - print(f"โŒ Unexpected error sending email: {type(e).__name__}: {e}") - return False - -def comment_on_pr(github_token, repo_full_name, pr_number, email): - print("๐Ÿ’ฌ Commenting on PR...") - comment_url = f"https://api.github.com/repos/{repo_full_name}/issues/{pr_number}/comments" - try: - comment_resp = requests.post( - comment_url, - headers={ - "Authorization": f"Bearer {github_token}", - "Accept": "application/vnd.github+json" - }, - json={ - "body": f"โœ… $10 OpenRouter API key sent to `{email}`. Thanks for your contribution to the goose cookbook!" - } - ) - comment_resp.raise_for_status() - print("โœ… Confirmation comment added to PR.") - except requests.exceptions.RequestException as e: - print("โŒ Failed to comment on PR:", str(e)) - raise - -if __name__ == "__main__": - main() diff --git a/.github/workflows/send-api-key.yml b/.github/workflows/send-api-key.yml deleted file mode 100644 index 5b1144acaa4d..000000000000 --- a/.github/workflows/send-api-key.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Send API Key on PR Merge - -on: - pull_request_target: - types: [closed] - paths: - - 'documentation/src/pages/recipes/data/recipes/**' - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - send-api-key: - if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - - steps: - - name: Checkout repo at merge commit - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.merge_commit_sha }} - fetch-depth: 0 - - - name: Check if recipe files were added or modified in merged PR - id: recipe_changes - run: | - set -e - echo "๐Ÿ” Checking if recipe files were added or modified in merged PR..." - - MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" - echo "Merge commit: $MERGE_COMMIT" - - # Get parent commit of the merge - PARENT_COMMIT=$(git rev-parse "$MERGE_COMMIT^1") - echo "Parent commit: $PARENT_COMMIT" - - # Get list of added or modified files in the PR (ignore deletions) - CHANGED_FILES=$(git diff --name-only --diff-filter=AM "$PARENT_COMMIT" "$MERGE_COMMIT") - - echo "Files added/modified in merged PR:" - echo "$CHANGED_FILES" - echo "" - - # Detect recipe changes only - if echo "$CHANGED_FILES" | grep -q "^documentation/src/pages/recipes/data/recipes/"; then - echo "recipe_files_changed=true" >> "$GITHUB_OUTPUT" - echo "โœ… Recipe files were added/modified in merged PR - proceeding with API key sending" - else - echo "recipe_files_changed=false" >> "$GITHUB_OUTPUT" - echo "โ„น๏ธ No recipe files were added/modified in merged PR - skipping API key sending" - fi - - - name: Set up Python - if: steps.recipe_changes.outputs.recipe_files_changed == 'true' - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install dependencies and run email script - if: steps.recipe_changes.outputs.recipe_files_changed == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_SHA: ${{ github.sha }} - GITHUB_REPOSITORY: ${{ github.repository }} - PROVISIONING_API_KEY: ${{ secrets.PROVISIONING_API_KEY }} - EMAIL_API_KEY: ${{ secrets.SENDGRID_API_KEY }} - run: | - pip install requests sendgrid email-validator - python .github/scripts/send_key.py diff --git a/.github/workflows/validate-recipe-pr.yml b/.github/workflows/validate-recipe-pr.yml deleted file mode 100644 index 04529c7f6348..000000000000 --- a/.github/workflows/validate-recipe-pr.yml +++ /dev/null @@ -1,265 +0,0 @@ -name: Validate Recipe PR - -on: - pull_request_target: - types: [opened, synchronize, reopened] - paths: - - 'documentation/src/pages/recipes/data/recipes/**' - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - validate-recipe: - runs-on: ubuntu-latest - - env: - PROVIDER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - - steps: - - name: Checkout PR - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install and Configure goose - run: | - mkdir -p /home/runner/.local/bin - curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh \ - | CONFIGURE=false INSTALL_PATH=/home/runner/.local/bin bash - echo "/home/runner/.local/bin" >> $GITHUB_PATH - - mkdir -p ~/.config/goose - cat < ~/.config/goose/config.yaml - GOOSE_PROVIDER: openrouter - GOOSE_MODEL: "anthropic/claude-sonnet-4" - keyring: false - EOF - - - name: Check if recipe files changed in this push - id: recipe_changes - run: | - set -e - echo "๐Ÿ” Checking if recipe files were modified in this push..." - - # Get the list of changed files in this specific push (added/modified only, not deleted) - if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then - # For synchronize events, check files changed since the previous commit - echo "๐Ÿ“ Synchronize event - checking files changed since previous commit" - CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }}) - else - # For opened/reopened, check all files in the PR (compare PR head against base) - echo "๐Ÿ“ PR opened/reopened - checking all files in PR" - CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}) - fi - - echo "Changed files in this push:" - echo "$CHANGED_FILES" - echo "" - - # Check if any recipe files were changed - if echo "$CHANGED_FILES" | grep -q "^documentation/src/pages/recipes/data/recipes/"; then - echo "recipe_files_changed=true" >> "$GITHUB_OUTPUT" - echo "โœ… Recipe files were modified in this push - proceeding with validation" - else - echo "recipe_files_changed=false" >> "$GITHUB_OUTPUT" - echo "โ„น๏ธ No recipe files were modified in this push - skipping validation" - fi - - - name: Find recipe files in PR (new or modified) - id: find_changed_recipes - if: steps.recipe_changes.outputs.recipe_files_changed == 'true' - run: | - set -e - echo "Looking for recipe files in PR (new or modified)..." - - # Get the list of changed/new files in this PR (added/modified only, not deleted) - if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then - # For synchronize events, check files changed since the previous commit - echo "๐Ÿ“ Synchronize event - checking files changed/added since previous commit" - CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }}) - else - # For opened/reopened, check all files in the PR (compare PR head against base) - echo "๐Ÿ“ PR opened/reopened - checking all new/modified files in PR" - CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}) - fi - - # Filter for recipe files only that were changed or added - RECIPE_FILES=$(echo "$CHANGED_FILES" | grep "^documentation/src/pages/recipes/data/recipes/" | grep -E "\.(yaml|yml)$" || true) - - if [ -z "$RECIPE_FILES" ]; then - echo "No changed recipe files found in PR" - echo "validation_status=no_files" >> $GITHUB_OUTPUT - exit 1 - fi - - echo "Found changed recipe files:" - echo "$RECIPE_FILES" - - # Save recipe file paths for validation step - echo "$RECIPE_FILES" > /tmp/changed_recipe_files.txt - - - name: Validate changed recipe files - id: validate - if: steps.recipe_changes.outputs.recipe_files_changed == 'true' - run: | - set -e - # Read the list of changed recipe files - RECIPE_FILES=$(cat /tmp/changed_recipe_files.txt) - - ALL_VALID=true - VALIDATION_OUTPUT="" - - # First pass: Basic YAML validation - while IFS= read -r RECIPE_FILE; do - if [ -f "$RECIPE_FILE" ]; then - BASE_RECIPE_FILENAME=$(basename "$RECIPE_FILE") - echo "๐Ÿ” Validating: $BASE_RECIPE_FILENAME" - if OUTPUT=$(goose recipe validate "$RECIPE_FILE" 2>&1); then - echo "โœ… Valid: $BASE_RECIPE_FILENAME" - VALIDATION_OUTPUT="${VALIDATION_OUTPUT}โœ… $BASE_RECIPE_FILENAME: VALID\n" - else - echo "โŒ Invalid: $BASE_RECIPE_FILENAME" - echo "$OUTPUT" - VALIDATION_OUTPUT="${VALIDATION_OUTPUT}โŒ $BASE_RECIPE_FILENAME: INVALID\n\`\`\`\n$OUTPUT\n\`\`\`\n" - ALL_VALID=false - fi - fi - done < /tmp/changed_recipe_files.txt - - # Second pass: Check for duplicate filenames - if [ "$ALL_VALID" = true ]; then - echo "๐Ÿ” Checking for duplicate filenames..." - - # Check for duplicate filenames first - SEEN_FILENAMES="" - while IFS= read -r RECIPE_FILE; do - if [ -f "$RECIPE_FILE" ]; then - FILENAME=$(basename "$RECIPE_FILE" .yaml) - FILENAME=$(basename "$FILENAME" .yml) - - echo "๐Ÿ“‹ Checking filename: '$FILENAME'" - - # Check if we've seen this filename before in this PR - if echo "$SEEN_FILENAMES" | grep -q "^$FILENAME$"; then - echo "โŒ Duplicate filename '$FILENAME' found in this PR" - VALIDATION_OUTPUT="${VALIDATION_OUTPUT}โŒ Duplicate filename '$FILENAME' found in this PR\n" - ALL_VALID=false - else - SEEN_FILENAMES="$SEEN_FILENAMES\n$FILENAME" - fi - - # Check if this is a new file or an update to existing file - # Get list of changed files in this PR compared to base branch - CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | grep "^$RECIPE_FILE$" || true) - EXISTING_FILES=$(find documentation/src/pages/recipes/data/recipes/ -name "$FILENAME.yaml" -o -name "$FILENAME.yml" | grep -v "^$RECIPE_FILE$" || true) - - if [ -n "$EXISTING_FILES" ] && [ -z "$CHANGED_FILES" ]; then - # File exists in repo but is not being modified - this is a new duplicate - echo "โŒ Recipe filename '$FILENAME' already exists:" - echo "$EXISTING_FILES" - VALIDATION_OUTPUT="${VALIDATION_OUTPUT}โŒ $RECIPE_FILE: Filename '$FILENAME' already exists in: $EXISTING_FILES\n" - ALL_VALID=false - elif [ -n "$EXISTING_FILES" ] && [ -n "$CHANGED_FILES" ]; then - # File exists and is being modified - this is an update - echo "โœ… Updating existing recipe: '$FILENAME'" - else - # File doesn't exist - this is a new recipe - echo "โœ… New recipe filename '$FILENAME' is unique" - fi - - echo "โœ… Filename '$FILENAME' validation complete" - fi - done < /tmp/changed_recipe_files.txt - fi - - # Save validation output for use in comment - echo "$VALIDATION_OUTPUT" > /tmp/validation_output.txt - - if [ "$ALL_VALID" = true ]; then - echo "validation_status=valid" >> $GITHUB_OUTPUT - else - echo "validation_status=invalid" >> $GITHUB_OUTPUT - fi - - - name: Comment validation results - if: steps.recipe_changes.outputs.recipe_files_changed == 'true' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - const status = '${{ steps.validate.outputs.validation_status }}'; - - let comment; - if (status === 'no_files') { - comment = `โŒ **Recipe Validation Failed** - - No recipe files found in the correct location! - - ๐Ÿ“ **Please add your recipe to**: \`documentation/src/pages/recipes/data/recipes/your-recipe-id.yaml\` - - **Example**: If your recipe ID is \`web-scraper\`, create: - \`documentation/src/pages/recipes/data/recipes/web-scraper.yaml\``; - } else if (status === 'valid') { - comment = `โœ… **Recipe Validation Passed** - - Your recipe(s) are valid and ready for review! - - ๐Ÿ” **Next Steps**: - 1. Our team will review your recipe - 2. If approved, we'll run a security scan - 3. Once merged, you'll receive $10 in OpenRouter credits (if email provided) - - Thanks for contributing to the goose Recipe Cookbook! ๐ŸŽ‰`; - } else { - // Read validation details from file - let validationDetails = ''; - try { - validationDetails = fs.readFileSync('/tmp/validation_output.txt', 'utf8'); - } catch (e) { - validationDetails = 'See workflow logs for details.'; - } - - comment = `โŒ **Recipe Validation Failed** - - Please fix the validation errors and push your changes: - - ${validationDetails} - - ๐Ÿ“š Check our [Recipe Guide](https://block.github.io/goose/recipes) for help with the correct format.`; - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: comment - }); - - - name: Set validation status - if: always() - run: | - # Check if recipe files were changed in this PR - if [ "${{ steps.recipe_changes.outputs.recipe_files_changed }}" = "false" ]; then - # No recipe files were modified in this PR - validation skipped - echo "โ„น๏ธ No recipe files in PR - validation skipped" - exit 0 - fi - - VALIDATION_STATUS="${{ steps.validate.outputs.validation_status }}" - if [ "$VALIDATION_STATUS" = "valid" ]; then - echo "โœ… All recipes are valid" - exit 0 - else - echo "โŒ Recipe validation failed" - exit 1 - fi \ No newline at end of file From 01893889d6563a82e03aac45bfad5ba2ae0be62b Mon Sep 17 00:00:00 2001 From: Ebony Louis Date: Mon, 8 Dec 2025 10:08:21 -0500 Subject: [PATCH 2/2] remove --- .github/pull_request_template.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bc90ec22d9a6..b7cd0bdd4b45 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -31,6 +31,3 @@ Before: After: -### Submitting a Recipe? - -**Email**: