From d9a537f5d59d05db8c862c61d16061e01e2775c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:24:48 +0000 Subject: [PATCH 1/5] Initial plan From 964885e8cf8fc7be9b7fce7cb79d129bc932ebc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:30:24 +0000 Subject: [PATCH 2/5] Add social preview image and setup documentation Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com> --- .github/SOCIAL_PREVIEW_SETUP.md | 128 ++++++++++++++++++++ .github/assets/social-preview.png | Bin 0 -> 49549 bytes README.md | 1 + scripts/generate_social_preview.py | 183 +++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 .github/SOCIAL_PREVIEW_SETUP.md create mode 100644 .github/assets/social-preview.png create mode 100755 scripts/generate_social_preview.py diff --git a/.github/SOCIAL_PREVIEW_SETUP.md b/.github/SOCIAL_PREVIEW_SETUP.md new file mode 100644 index 0000000..4d261c7 --- /dev/null +++ b/.github/SOCIAL_PREVIEW_SETUP.md @@ -0,0 +1,128 @@ +# Social Preview Image Setup + +This guide explains how to set up the social preview image for the PrivaseeAI.Security repository on GitHub. + +## About the Image + +The social preview image (`social-preview.png`) is an architectural diagram that displays when the repository is shared on social media platforms like Twitter, LinkedIn, or Facebook. + +**Image Details:** +- **Dimensions:** 1280×640 pixels (GitHub recommended size) +- **Format:** PNG +- **Size:** ~49KB +- **Location:** `.github/assets/social-preview.png` + +**Features Highlighted:** +- PrivaseeAI.Security branding +- Real-Time iOS Threat Detection & Monitoring subtitle +- Architectural diagram showing system components: + - CLI Interface + - Threat Orchestrator + - VPN Integrity Monitor + - API Abuse Monitor + - Carrier Compromise Detector + - Backup Monitor + - Telegram Alerter +- Key statistics: Battle-Tested, Privacy-First, Real-Time, 196 Tests + +## How to Set the Social Preview on GitHub + +**Note:** Setting the social preview image requires repository administrator access and must be done through the GitHub web interface. + +### Step-by-Step Instructions: + +1. **Navigate to Repository Settings** + - Go to https://github.com/aurelianware/PrivaseeAI.Security + - Click on the **Settings** tab (requires admin access) + +2. **Find the Social Preview Section** + - Scroll down to the **Social preview** section + - It's located near the top of the Settings page + +3. **Upload the Image** + - Click **Edit** next to the social preview section + - Click **Upload an image...** + - Select the file: `.github/assets/social-preview.png` from your local clone + - Alternatively, download it from the repository first, then upload it + +4. **Verify and Save** + - Preview the image in the dialog + - Click **Save** or **Set as preview** to confirm + - The image should now appear in the Social preview section + +### Verification + +After uploading, you can verify the social preview is working: + +1. **Check the Settings page** - The preview should display in the Social preview section +2. **Share on social media** - Share the repository URL on Twitter, LinkedIn, or Facebook to see the preview in action +3. **Use Twitter Card Validator** - Visit https://cards-dev.twitter.com/validator and enter your repo URL +4. **Use LinkedIn Post Inspector** - Visit https://www.linkedin.com/post-inspector/ and enter your repo URL + +## Image Requirements (Reference) + +GitHub's requirements for social preview images: + +- **Minimum dimensions:** 640×320 pixels +- **Recommended dimensions:** 1280×640 pixels (what we use) +- **Maximum file size:** 1 MB +- **Supported formats:** PNG, JPG, GIF +- **Aspect ratio:** 2:1 (width:height) + +Our image meets all these requirements. + +## Updating the Image + +If you need to update the social preview image in the future: + +1. **Regenerate the image** using the Python script (see below) +2. **Replace** `.github/assets/social-preview.png` in the repository +3. **Re-upload** to GitHub following the steps above + +### Regenerating the Image + +The image was created using Python and Pillow. To recreate or modify it: + +```bash +# Install Pillow if needed +pip install Pillow + +# Run the generation script (if available in scripts/) +python scripts/generate-social-preview.py + +# Or create your own script based on the original +``` + +The original script used: +- **Background:** GitHub dark theme (#0D1117) +- **Primary color:** GitHub blue (#58A6FF) +- **Secondary color:** GitHub green (#7EE787) +- **Accent color:** GitHub red (#F85149) +- **Purple color:** GitHub purple (#BC8CFF) +- **Font:** DejaVu Sans (Bold for title, Regular for text) + +## Troubleshooting + +**Issue:** Can't find the Settings tab +- **Solution:** You need repository administrator access. Contact the repository owner. + +**Issue:** Image doesn't appear after uploading +- **Solution:** Clear your browser cache and reload. It may take a few minutes to propagate. + +**Issue:** Image looks blurry or pixelated +- **Solution:** Ensure you're uploading the full-resolution 1280×640 image, not a scaled-down version. + +**Issue:** Image is rejected during upload +- **Solution:** Verify the file is under 1 MB and in PNG, JPG, or GIF format. + +## Resources + +- [GitHub Docs: Customizing your repository's social media preview](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/customizing-your-repositorys-social-media-preview) +- [Open Graph Protocol](https://ogp.me/) +- [Twitter Card Validator](https://cards-dev.twitter.com/validator) + +## Questions? + +If you have questions about the social preview image setup, please: +- Open an issue in the repository +- Contact: support@aurelianware.com diff --git a/.github/assets/social-preview.png b/.github/assets/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..97255005726eb6ab934d0bb3363e9906af8d7585 GIT binary patch literal 49549 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 a2bhbyu0EqVuUu}2L+SY|`7$}v&;J84yf`WV literal 0 HcmV?d00001 diff --git a/README.md b/README.md index b693c0e..e4d2230 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ echo "TELEGRAM_CHAT_ID=your_chat_id" >> .env - **[CONTRIBUTING.md](CONTRIBUTING.md)** - How to contribute - **[SECURITY.md](SECURITY.md)** - Security policy and vulnerability reporting - **[TESTING_SUMMARY.md](TESTING_SUMMARY.md)** - Test infrastructure overview +- **[.github/SOCIAL_PREVIEW_SETUP.md](.github/SOCIAL_PREVIEW_SETUP.md)** - Social media preview image setup ## 🎯 Use Cases diff --git a/scripts/generate_social_preview.py b/scripts/generate_social_preview.py new file mode 100755 index 0000000..274416c --- /dev/null +++ b/scripts/generate_social_preview.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +Generate social preview image for PrivaseeAI.Security repository + +This script creates a 1280x640 PNG image with an architectural diagram +showing the main components of the PrivaseeAI.Security system. + +Usage: + python scripts/generate_social_preview.py [--output OUTPUT_PATH] + +Requirements: + pip install Pillow + +The generated image can be uploaded to GitHub as the repository's +social preview image (Settings > Social preview). +""" + +import argparse +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont + + +# Image dimensions (GitHub recommended: 1280x640) +WIDTH = 1280 +HEIGHT = 640 + +# Color scheme - Security/Privacy theme (GitHub dark colors) +BG_COLOR = "#0D1117" # GitHub dark background +PRIMARY_COLOR = "#58A6FF" # GitHub blue +SECONDARY_COLOR = "#7EE787" # GitHub green +TEXT_COLOR = "#C9D1D9" # GitHub text +ACCENT_COLOR = "#F85149" # GitHub red (for alerts/threats) +PURPLE_COLOR = "#BC8CFF" # GitHub purple + + +def hex_to_rgb(hex_color): + """Convert hex color to RGB tuple""" + hex_color = hex_color.lstrip('#') + return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + + +def create_social_preview(output_path): + """ + Create the social preview image + + Args: + output_path: Path where the image will be saved + """ + + # Create image with dark background + img = Image.new('RGB', (WIDTH, HEIGHT), hex_to_rgb(BG_COLOR)) + draw = ImageDraw.Draw(img) + + # Try to use a nice font, fallback to default + try: + title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72) + subtitle_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32) + text_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24) + small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) + except IOError: + # Fallback to default font + print("Warning: DejaVu Sans font not found, using default font") + title_font = ImageFont.load_default() + subtitle_font = ImageFont.load_default() + text_font = ImageFont.load_default() + small_font = ImageFont.load_default() + + # Title + title = "PrivaseeAI.Security" + draw.text((50, 40), title, fill=hex_to_rgb(PRIMARY_COLOR), font=title_font) + + # Subtitle + subtitle = "Real-Time iOS Threat Detection & Monitoring" + draw.text((50, 130), subtitle, fill=hex_to_rgb(TEXT_COLOR), font=subtitle_font) + + # Architecture diagram + y_offset = 200 + + # Top: CLI Interface + draw.rectangle([50, y_offset, 1230, y_offset + 60], + outline=hex_to_rgb(PRIMARY_COLOR), width=3) + draw.text((520, y_offset + 20), "CLI Interface", + fill=hex_to_rgb(PRIMARY_COLOR), font=text_font) + + # Middle: Orchestrator + y_offset += 80 + draw.rectangle([400, y_offset, 880, y_offset + 50], + outline=hex_to_rgb(SECONDARY_COLOR), width=3) + draw.text((520, y_offset + 15), "Threat Orchestrator", + fill=hex_to_rgb(SECONDARY_COLOR), font=text_font) + + # Bottom: Monitors (4 boxes) + y_offset += 70 + box_width = 250 + box_height = 60 + spacing = 50 + + monitors = [ + ("VPN\nIntegrity", PRIMARY_COLOR), + ("API\nAbuse", PURPLE_COLOR), + ("Carrier\nCompromise", ACCENT_COLOR), + ("Backup\nMonitor", SECONDARY_COLOR) + ] + + x_start = 80 + for i, (monitor, color) in enumerate(monitors): + x = x_start + i * (box_width + spacing) + draw.rectangle([x, y_offset, x + box_width, y_offset + box_height], + outline=hex_to_rgb(color), width=3) + lines = monitor.split('\n') + for j, line in enumerate(lines): + draw.text((x + 60, y_offset + 10 + j * 25), line, + fill=hex_to_rgb(color), font=small_font) + + # Alert system at bottom + y_offset += 90 + draw.rectangle([400, y_offset, 880, y_offset + 50], + outline=hex_to_rgb(ACCENT_COLOR), width=3) + draw.text((520, y_offset + 15), "Telegram Alerter", + fill=hex_to_rgb(ACCENT_COLOR), font=text_font) + + # Add arrows/connections (simplified) + # Arrow from CLI to Orchestrator + draw.line([(640, 260), (640, 280)], fill=hex_to_rgb(TEXT_COLOR), width=2) + + # Arrows from Orchestrator to monitors + orch_center_x = 640 + orch_y = 330 + monitor_y = 350 + + for i in range(4): + monitor_x = x_start + box_width // 2 + i * (box_width + spacing) + draw.line([(orch_center_x, orch_y), (monitor_x, monitor_y)], + fill=hex_to_rgb(TEXT_COLOR), width=2) + + # Arrows from monitors to alerter + alert_y = 510 + for i in range(4): + monitor_x = x_start + box_width // 2 + i * (box_width + spacing) + draw.line([(monitor_x, y_offset - 30), (640, alert_y)], + fill=hex_to_rgb(TEXT_COLOR), width=2) + + # Footer with key features + y_offset = 580 + features = "🛡️ Battle-Tested • 🔒 Privacy-First • ⚡ Real-Time • ✅ 196 Tests" + draw.text((180, y_offset), features, fill=hex_to_rgb(TEXT_COLOR), font=small_font) + + # Save the image + img.save(output_path) + print(f"Social preview image created: {output_path}") + print(f"Dimensions: {WIDTH}x{HEIGHT}") + print(f"File size: {Path(output_path).stat().st_size / 1024:.1f} KB") + + +def main(): + """Main entry point""" + parser = argparse.ArgumentParser( + description='Generate social preview image for PrivaseeAI.Security repository' + ) + parser.add_argument( + '--output', + default='.github/assets/social-preview.png', + help='Output path for the generated image (default: .github/assets/social-preview.png)' + ) + + args = parser.parse_args() + + # Ensure output directory exists + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the image + create_social_preview(str(output_path)) + + print("\nNext steps:") + print("1. Review the generated image") + print("2. Go to GitHub repository Settings > Social preview") + print("3. Upload the image") + print("4. See .github/SOCIAL_PREVIEW_SETUP.md for detailed instructions") + + +if __name__ == '__main__': + main() From 6dd6ca78f4ea23b89ec01ee1352ae5d393eb1f84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:32:30 +0000 Subject: [PATCH 3/5] Fix script filename in documentation Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com> --- .github/SOCIAL_PREVIEW_SETUP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/SOCIAL_PREVIEW_SETUP.md b/.github/SOCIAL_PREVIEW_SETUP.md index 4d261c7..c86d975 100644 --- a/.github/SOCIAL_PREVIEW_SETUP.md +++ b/.github/SOCIAL_PREVIEW_SETUP.md @@ -87,8 +87,8 @@ The image was created using Python and Pillow. To recreate or modify it: # Install Pillow if needed pip install Pillow -# Run the generation script (if available in scripts/) -python scripts/generate-social-preview.py +# Run the generation script +python scripts/generate_social_preview.py # Or create your own script based on the original ``` From add338a350bba6c51ded14071495f281a060b559 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 04:00:23 +0000 Subject: [PATCH 4/5] Address code review feedback: improve cross-platform support, add error handling, extract constants, and add Pillow to dev dependencies Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com> --- requirements-dev.txt | 3 + scripts/generate_social_preview.py | 228 +++++++++++++++++++++-------- 2 files changed, 173 insertions(+), 58 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7b8e2bc..a4da927 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,3 +17,6 @@ mypy>=1.0.0 # Development utilities ipython>=8.12.0 ipdb>=0.13.13 + +# Image generation (for social preview) +Pillow>=10.0.0 diff --git a/scripts/generate_social_preview.py b/scripts/generate_social_preview.py index 274416c..6548d47 100755 --- a/scripts/generate_social_preview.py +++ b/scripts/generate_social_preview.py @@ -5,19 +5,32 @@ This script creates a 1280x640 PNG image with an architectural diagram showing the main components of the PrivaseeAI.Security system. -Usage: - python scripts/generate_social_preview.py [--output OUTPUT_PATH] +This is a development/maintenance utility and requires the Pillow +library (PIL), which is not installed by default. -Requirements: +Install with: pip install Pillow +Usage: + python scripts/generate_social_preview.py [--output OUTPUT_PATH] + The generated image can be uploaded to GitHub as the repository's social preview image (Settings > Social preview). """ import argparse from pathlib import Path -from PIL import Image, ImageDraw, ImageFont +import sys + +try: + from PIL import Image, ImageDraw, ImageFont +except ImportError as exc: + sys.stderr.write( + "Error: The Pillow library (PIL) is required to run this script.\n" + "Install it with:\n" + " pip install Pillow\n" + ) + raise SystemExit(1) from exc # Image dimensions (GitHub recommended: 1280x640) @@ -32,6 +45,83 @@ ACCENT_COLOR = "#F85149" # GitHub red (for alerts/threats) PURPLE_COLOR = "#BC8CFF" # GitHub purple +# Layout constants - extracted for maintainability +# Margins and positions +MARGIN_LEFT = 50 +TITLE_Y = 40 +SUBTITLE_Y = 130 + +# CLI Interface box +CLI_Y = 200 +CLI_RIGHT = 1230 +CLI_HEIGHT = 60 +CLI_TEXT_X = 520 +CLI_TEXT_Y_OFFSET = 20 + +# Orchestrator box +ORCH_Y_SPACING = 80 +ORCH_LEFT = 400 +ORCH_RIGHT = 880 +ORCH_HEIGHT = 50 +ORCH_TEXT_X = 520 +ORCH_TEXT_Y_OFFSET = 15 +ORCH_CENTER_X = 640 +ORCH_ARROW_Y = 330 + +# Monitor boxes +MONITOR_Y_SPACING = 70 +MONITOR_BOX_WIDTH = 250 +MONITOR_BOX_HEIGHT = 60 +MONITOR_SPACING = 50 +MONITOR_X_START = 80 +MONITOR_TEXT_X_OFFSET = 60 +MONITOR_TEXT_Y_OFFSET = 10 +MONITOR_TEXT_LINE_HEIGHT = 25 +MONITOR_ARROW_Y = 350 + +# Alerter box +ALERTER_Y_SPACING = 90 +ALERTER_LEFT = 400 +ALERTER_RIGHT = 880 +ALERTER_HEIGHT = 50 +ALERTER_TEXT_X = 520 +ALERTER_TEXT_Y_OFFSET = 15 +ALERTER_ARROW_Y = 510 +ALERTER_ARROW_Y_OFFSET = 30 + +# Footer +FOOTER_Y = 580 +FOOTER_X = 180 + +# Arrow constants +CLI_ORCH_ARROW_START_Y = 260 +CLI_ORCH_ARROW_END_Y = 280 +ARROW_WIDTH = 2 + +# Box border width +BOX_BORDER_WIDTH = 3 + +# Font sizes +TITLE_FONT_SIZE = 72 +SUBTITLE_FONT_SIZE = 32 +TEXT_FONT_SIZE = 24 +SMALL_FONT_SIZE = 20 + +# Common font paths across different operating systems +FONT_PATHS = [ + # Linux + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + # macOS + "/System/Library/Fonts/Supplemental/Arial Bold.ttf", + "/System/Library/Fonts/Supplemental/Arial.ttf", + "/Library/Fonts/Arial Bold.ttf", + "/Library/Fonts/Arial.ttf", + # Windows (via Wine or WSL) + "C:\\Windows\\Fonts\\arialbd.ttf", + "C:\\Windows\\Fonts\\arial.ttf", +] + def hex_to_rgb(hex_color): """Convert hex color to RGB tuple""" @@ -39,6 +129,28 @@ def hex_to_rgb(hex_color): return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) +def find_font(font_paths, size): + """ + Try to find a working font from a list of paths. + + Args: + font_paths: List of font file paths to try + size: Font size + + Returns: + ImageFont object (truetype if found, default otherwise) + """ + for font_path in font_paths: + try: + return ImageFont.truetype(font_path, size) + except (IOError, OSError): + continue + + # No fonts found, return default + print(f"Warning: Could not find fonts at standard locations, using default font") + return ImageFont.load_default() + + def create_social_preview(output_path): """ Create the social preview image @@ -51,49 +163,42 @@ def create_social_preview(output_path): img = Image.new('RGB', (WIDTH, HEIGHT), hex_to_rgb(BG_COLOR)) draw = ImageDraw.Draw(img) - # Try to use a nice font, fallback to default - try: - title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72) - subtitle_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32) - text_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24) - small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) - except IOError: - # Fallback to default font - print("Warning: DejaVu Sans font not found, using default font") - title_font = ImageFont.load_default() - subtitle_font = ImageFont.load_default() - text_font = ImageFont.load_default() - small_font = ImageFont.load_default() + # Load fonts with cross-platform support + # Try bold fonts first for title + bold_font_paths = [p for p in FONT_PATHS if 'Bold' in p or 'bold' in p] + regular_font_paths = [p for p in FONT_PATHS if 'Bold' not in p and 'bold' not in p] + + title_font = find_font(bold_font_paths, TITLE_FONT_SIZE) + subtitle_font = find_font(regular_font_paths, SUBTITLE_FONT_SIZE) + text_font = find_font(regular_font_paths, TEXT_FONT_SIZE) + small_font = find_font(regular_font_paths, SMALL_FONT_SIZE) # Title title = "PrivaseeAI.Security" - draw.text((50, 40), title, fill=hex_to_rgb(PRIMARY_COLOR), font=title_font) + draw.text((MARGIN_LEFT, TITLE_Y), title, fill=hex_to_rgb(PRIMARY_COLOR), font=title_font) # Subtitle subtitle = "Real-Time iOS Threat Detection & Monitoring" - draw.text((50, 130), subtitle, fill=hex_to_rgb(TEXT_COLOR), font=subtitle_font) + draw.text((MARGIN_LEFT, SUBTITLE_Y), subtitle, fill=hex_to_rgb(TEXT_COLOR), font=subtitle_font) # Architecture diagram - y_offset = 200 + y_offset = CLI_Y # Top: CLI Interface - draw.rectangle([50, y_offset, 1230, y_offset + 60], - outline=hex_to_rgb(PRIMARY_COLOR), width=3) - draw.text((520, y_offset + 20), "CLI Interface", + draw.rectangle([MARGIN_LEFT, y_offset, CLI_RIGHT, y_offset + CLI_HEIGHT], + outline=hex_to_rgb(PRIMARY_COLOR), width=BOX_BORDER_WIDTH) + draw.text((CLI_TEXT_X, y_offset + CLI_TEXT_Y_OFFSET), "CLI Interface", fill=hex_to_rgb(PRIMARY_COLOR), font=text_font) # Middle: Orchestrator - y_offset += 80 - draw.rectangle([400, y_offset, 880, y_offset + 50], - outline=hex_to_rgb(SECONDARY_COLOR), width=3) - draw.text((520, y_offset + 15), "Threat Orchestrator", + y_offset += ORCH_Y_SPACING + draw.rectangle([ORCH_LEFT, y_offset, ORCH_RIGHT, y_offset + ORCH_HEIGHT], + outline=hex_to_rgb(SECONDARY_COLOR), width=BOX_BORDER_WIDTH) + draw.text((ORCH_TEXT_X, y_offset + ORCH_TEXT_Y_OFFSET), "Threat Orchestrator", fill=hex_to_rgb(SECONDARY_COLOR), font=text_font) # Bottom: Monitors (4 boxes) - y_offset += 70 - box_width = 250 - box_height = 60 - spacing = 50 + y_offset += MONITOR_Y_SPACING monitors = [ ("VPN\nIntegrity", PRIMARY_COLOR), @@ -102,54 +207,61 @@ def create_social_preview(output_path): ("Backup\nMonitor", SECONDARY_COLOR) ] - x_start = 80 for i, (monitor, color) in enumerate(monitors): - x = x_start + i * (box_width + spacing) - draw.rectangle([x, y_offset, x + box_width, y_offset + box_height], - outline=hex_to_rgb(color), width=3) + x = MONITOR_X_START + i * (MONITOR_BOX_WIDTH + MONITOR_SPACING) + draw.rectangle([x, y_offset, x + MONITOR_BOX_WIDTH, y_offset + MONITOR_BOX_HEIGHT], + outline=hex_to_rgb(color), width=BOX_BORDER_WIDTH) lines = monitor.split('\n') for j, line in enumerate(lines): - draw.text((x + 60, y_offset + 10 + j * 25), line, - fill=hex_to_rgb(color), font=small_font) + draw.text((x + MONITOR_TEXT_X_OFFSET, y_offset + MONITOR_TEXT_Y_OFFSET + j * MONITOR_TEXT_LINE_HEIGHT), + line, fill=hex_to_rgb(color), font=small_font) # Alert system at bottom - y_offset += 90 - draw.rectangle([400, y_offset, 880, y_offset + 50], - outline=hex_to_rgb(ACCENT_COLOR), width=3) - draw.text((520, y_offset + 15), "Telegram Alerter", + y_offset += ALERTER_Y_SPACING + draw.rectangle([ALERTER_LEFT, y_offset, ALERTER_RIGHT, y_offset + ALERTER_HEIGHT], + outline=hex_to_rgb(ACCENT_COLOR), width=BOX_BORDER_WIDTH) + draw.text((ALERTER_TEXT_X, y_offset + ALERTER_TEXT_Y_OFFSET), "Telegram Alerter", fill=hex_to_rgb(ACCENT_COLOR), font=text_font) # Add arrows/connections (simplified) # Arrow from CLI to Orchestrator - draw.line([(640, 260), (640, 280)], fill=hex_to_rgb(TEXT_COLOR), width=2) + draw.line([(ORCH_CENTER_X, CLI_ORCH_ARROW_START_Y), (ORCH_CENTER_X, CLI_ORCH_ARROW_END_Y)], + fill=hex_to_rgb(TEXT_COLOR), width=ARROW_WIDTH) # Arrows from Orchestrator to monitors - orch_center_x = 640 - orch_y = 330 - monitor_y = 350 - for i in range(4): - monitor_x = x_start + box_width // 2 + i * (box_width + spacing) - draw.line([(orch_center_x, orch_y), (monitor_x, monitor_y)], - fill=hex_to_rgb(TEXT_COLOR), width=2) + monitor_x = MONITOR_X_START + MONITOR_BOX_WIDTH // 2 + i * (MONITOR_BOX_WIDTH + MONITOR_SPACING) + draw.line([(ORCH_CENTER_X, ORCH_ARROW_Y), (monitor_x, MONITOR_ARROW_Y)], + fill=hex_to_rgb(TEXT_COLOR), width=ARROW_WIDTH) # Arrows from monitors to alerter - alert_y = 510 for i in range(4): - monitor_x = x_start + box_width // 2 + i * (box_width + spacing) - draw.line([(monitor_x, y_offset - 30), (640, alert_y)], - fill=hex_to_rgb(TEXT_COLOR), width=2) + monitor_x = MONITOR_X_START + MONITOR_BOX_WIDTH // 2 + i * (MONITOR_BOX_WIDTH + MONITOR_SPACING) + draw.line([(monitor_x, y_offset - ALERTER_ARROW_Y_OFFSET), (ORCH_CENTER_X, ALERTER_ARROW_Y)], + fill=hex_to_rgb(TEXT_COLOR), width=ARROW_WIDTH) # Footer with key features - y_offset = 580 features = "🛡️ Battle-Tested • 🔒 Privacy-First • ⚡ Real-Time • ✅ 196 Tests" - draw.text((180, y_offset), features, fill=hex_to_rgb(TEXT_COLOR), font=small_font) + draw.text((FOOTER_X, FOOTER_Y), features, fill=hex_to_rgb(TEXT_COLOR), font=small_font) - # Save the image - img.save(output_path) + # Save the image with error handling + try: + img.save(output_path) + except OSError as e: + print(f"Error: Failed to save image to '{output_path}': {e}") + return + + # Try to determine file size; if it fails, continue without it + try: + file_size_kb = Path(output_path).stat().st_size / 1024 + except OSError as e: + print(f"Warning: Image saved but failed to retrieve file size for '{output_path}': {e}") + file_size_kb = None + print(f"Social preview image created: {output_path}") print(f"Dimensions: {WIDTH}x{HEIGHT}") - print(f"File size: {Path(output_path).stat().st_size / 1024:.1f} KB") + if file_size_kb is not None: + print(f"File size: {file_size_kb:.1f} KB") def main(): From 54047ba05865576b43192b59a1ea95c1eade8a27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 04:02:25 +0000 Subject: [PATCH 5/5] Improve error reporting: use stderr and exit with error code on save failure Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com> --- scripts/generate_social_preview.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/generate_social_preview.py b/scripts/generate_social_preview.py index 6548d47..dae97b3 100755 --- a/scripts/generate_social_preview.py +++ b/scripts/generate_social_preview.py @@ -147,7 +147,7 @@ def find_font(font_paths, size): continue # No fonts found, return default - print(f"Warning: Could not find fonts at standard locations, using default font") + sys.stderr.write("Warning: Could not find fonts at standard locations, using default font\n") return ImageFont.load_default() @@ -248,14 +248,14 @@ def create_social_preview(output_path): try: img.save(output_path) except OSError as e: - print(f"Error: Failed to save image to '{output_path}': {e}") - return + sys.stderr.write(f"Error: Failed to save image to '{output_path}': {e}\n") + sys.exit(1) # Try to determine file size; if it fails, continue without it try: file_size_kb = Path(output_path).stat().st_size / 1024 except OSError as e: - print(f"Warning: Image saved but failed to retrieve file size for '{output_path}': {e}") + sys.stderr.write(f"Warning: Image saved but failed to retrieve file size for '{output_path}': {e}\n") file_size_kb = None print(f"Social preview image created: {output_path}")