commit 4318c8f64277867f1a8c42445b0cc658b8751b22 Author: mblanke Date: Tue Feb 10 16:36:30 2026 -0500 Initial commit with dev backbone template diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..02dd7f9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,57 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +*.egg-info/ +.pytest_cache/ +.coverage + +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.next/ +out/ +.turbo/ +.vercel/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Docs +*.md +!DOCKER_README.md + +# Test files +test_*.py +*_test.py + +# Excel files (not needed in container) +*.xlsx + +# Email config (sensitive) +.env +*.pem +*.key + +# Misc +*.log +.cache/ diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..cab233c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,9 @@ + +Follow `AGENTS.md` and `SKILLS.md`. + +Rules: +- Use the PLAN → IMPLEMENT → VERIFY → REVIEW loop. +- Keep model selection on Auto unless AGENTS.md role routing says to override for that role. +- Never claim "done" unless DoD passes (`./scripts/dod.sh` or `\scripts\dod.ps1`). +- Keep diffs small and add/update tests when behavior changes. +- Prefer reproducible commands and cite sources for generated documents. diff --git a/.github/workflows/dod.yml b/.github/workflows/dod.yml new file mode 100644 index 0000000..7cc778d --- /dev/null +++ b/.github/workflows/dod.yml @@ -0,0 +1,17 @@ + +name: DoD Gate + +on: + pull_request: + push: + branches: [ main, master ] + +jobs: + dod: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run DoD + run: | + chmod +x ./scripts/dod.sh || true + ./scripts/dod.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7d02af --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python +.venv/ +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ + +# IDE +.vscode/ +.idea/ + +# Environment +.env +.env.local + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Node +node_modules/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fb9c97a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,78 @@ + +# Agent Operating System (Auto-first) + +Default: use **Auto** model selection. Only override the model when a role below says it is worth it. + +## Prime directive +- Never claim "done" unless **DoD passes**. +- Prefer small, reviewable diffs. +- If requirements are unclear: stop and produce a PLAN + questions inside the plan. +- Agents talk; **DoD decides**. + +## Always follow this loop +1) PLAN: goal, constraints, assumptions, steps (≤10), files to touch, test plan. +2) IMPLEMENT: smallest correct change. +3) VERIFY: run DoD until green. +4) REVIEW: summarize changes, risks, next steps. + +## DoD Gate (Definition of Done) +Required before "done": +- macOS/Linux: `./scripts/dod.sh` +- Windows: `\scripts\dod.ps1` + +If DoD cannot be run, say exactly why and what would be run. + +## Terminal agent workflow (Copilot CLI) +Preferred terminal assistant: GitHub Copilot CLI via `gh copilot`. + +Default loop: +1) Plan: draft plan + file list + test plan. +2) Build: implement smallest slice. +3) Steer: when stuck, ask for next action using current errors/logs. +4) Verify: run DoD until green. + +Rules: +- Keep diffs small. +- If the same error repeats twice, switch to Reviewer role and produce a fix plan. + +## Role routing (choose a role explicitly) + +### Planner +Use when: new feature, refactor, multi-file, uncertain scope. +Output: plan + acceptance criteria + risks + test plan. +Model: Auto (override to a “high reasoning / Codex” model only for complex design/debugging). + +### UI/UX Specialist +Use when: screens, layout, copy, design tradeoffs, component structure. +Output: component outline, UX notes, acceptance criteria. +Model: Auto (override to Gemini only when UI/UX is the main work). + +### Coder +Use when: writing/editing code, plumbing, tests, small refactors. +Rules: follow repo conventions; keep diff small; add/update tests when behavior changes. +Model: Auto (override to Claude Haiku only when speed matters and the change is well-scoped). + +### Reviewer +Use when: before merge, failing tests, risky changes, security-sensitive areas. +Output: concrete issues + recommended fixes + risk assessment + verification suggestions. +Model: Auto (override to the strongest available for high-stakes diffs). + +## Non-negotiables +- Do not expose secrets/tokens/keys. Never print env vars. +- No destructive commands unless explicitly required and narrowly scoped. +- Do not add new dependencies without stating why + impact + alternatives. +- Prefer deterministic, reproducible steps. +- Cite sources when generating documents from a knowledge base. + +## Repo facts (fill these in) +- Primary stack: +- Package manager: +- Test command: +- Lint/format command: +- Build command (if any): +- Deployment (if any): + + +## Claude Code Agents (optional) +- `.claude/agents/architect-cyber.md` — architecture + security + ops decisions for cyber apps. +- Add more agents in `.claude/agents/` as you standardize roles (reviewer, tester, security-lens). diff --git a/DOCKER_QUICKSTART.md b/DOCKER_QUICKSTART.md new file mode 100644 index 0000000..d83d751 --- /dev/null +++ b/DOCKER_QUICKSTART.md @@ -0,0 +1,255 @@ +# 🐋 Docker Setup Complete! + +## What's Been Created + +### Docker Files +- ✅ `Dockerfile.backend` - Flask API with Playwright +- ✅ `Dockerfile.frontend` - Next.js app optimized for production +- ✅ `Dockerfile.email` - Email scheduler service +- ✅ `docker-compose.yml` - Development setup +- ✅ `docker-compose.prod.yml` - Production setup with nginx +- ✅ `.dockerignore` - Optimized build context +- ✅ `requirements.txt` - Python dependencies + +### Configuration +- ✅ Updated `next.config.ts` for standalone output +- ✅ Created startup scripts (Windows & Linux) +- ✅ Complete documentation in `DOCKER_README.md` + +--- + +## 🚀 Quick Start + +### Option 1: Windows Script (Easiest) +```bash +docker-start.bat +``` + +### Option 2: Docker Compose +```bash +docker-compose up -d +``` + +### Option 3: Manual Build +```bash +# Build +docker-compose build + +# Start +docker-compose up -d + +# Check status +docker-compose ps +``` + +--- + +## 📦 What Gets Deployed + +### Backend Container +- Python 3.13 +- Flask API on port 5000 +- Playwright with Chromium browser +- Lottery scrapers for all 4 lotteries +- Investment calculator +- Health check endpoint + +### Frontend Container +- Node.js 20 +- Next.js standalone build +- Optimized production bundle +- Connects to backend API + +### Email Container (Optional) +- Runs daily at 7:00 AM +- Sends lottery jackpot emails +- Uses same scraping logic + +--- + +## 🌐 Access Points + +After running `docker-compose up -d`: + +- **Frontend**: http://localhost:3000 +- **Backend API**: http://localhost:5000 +- **Health Check**: http://localhost:5000/api/health + +--- + +## 📊 Container Management + +### View Logs +```bash +# All services +docker-compose logs -f + +# Specific service +docker-compose logs -f backend +docker-compose logs -f frontend +``` + +### Restart Services +```bash +docker-compose restart +``` + +### Stop Everything +```bash +docker-compose down +``` + +### Rebuild After Changes +```bash +docker-compose up -d --build +``` + +--- + +## 🔧 Troubleshooting + +### Port Already in Use +If ports 3000 or 5000 are busy: + +**Option A**: Stop other services +```bash +# Windows +netstat -ano | findstr :3000 +taskkill /PID /F +``` + +**Option B**: Change ports in `docker-compose.yml` +```yaml +ports: + - "8080:3000" # Use port 8080 instead +``` + +### Backend Won't Start +```bash +# Check logs +docker logs lottery-backend + +# Rebuild without cache +docker-compose build --no-cache backend +``` + +### Frontend Can't Connect +Update `docker-compose.yml` frontend environment: +```yaml +environment: + - NEXT_PUBLIC_API_URL=http://localhost:5000 +``` + +--- + +## 🎯 Include Email Scheduler + +To run the email scheduler: + +```bash +docker-compose --profile email up -d +``` + +Or remove the `profiles` section from `docker-compose.yml` to always include it. + +--- + +## 📈 Production Deployment + +### Use Production Compose +```bash +docker-compose -f docker-compose.prod.yml up -d +``` + +### Deploy to Server +```bash +# On your server +git clone +cd Lottery +docker-compose -f docker-compose.prod.yml up -d +``` + +### Push to Docker Hub +```bash +# Login +docker login + +# Tag images +docker tag lottery-backend yourusername/lottery-backend:latest +docker tag lottery-frontend yourusername/lottery-frontend:latest + +# Push +docker push yourusername/lottery-backend:latest +docker push yourusername/lottery-frontend:latest +``` + +--- + +## 🔒 Security for Production + +1. **Use environment variables** - Don't hardcode credentials +2. **Enable HTTPS** - Use nginx with SSL certificates +3. **Update base images** regularly +4. **Scan for vulnerabilities**: + ```bash + docker scan lottery-backend + ``` +5. **Use Docker secrets** for sensitive data + +--- + +## 💾 Data Persistence + +Currently, containers are stateless. To add persistence: + +Add volumes in `docker-compose.yml`: +```yaml +volumes: + - ./data:/app/data +``` + +--- + +## 🎛️ Resource Limits + +Current limits (production): +- Backend: 2GB RAM, 1 CPU +- Frontend: 512MB RAM, 0.5 CPU +- Email: 1GB RAM, 0.5 CPU + +Adjust in `docker-compose.prod.yml` if needed. + +--- + +## ✅ Benefits of Docker + +1. ✅ **Consistent environment** - Works the same everywhere +2. ✅ **Easy deployment** - One command to start everything +3. ✅ **Isolation** - Each service in its own container +4. ✅ **Scalability** - Easy to scale services +5. ✅ **Version control** - Docker images are versioned +6. ✅ **Portability** - Deploy anywhere Docker runs + +--- + +## 📝 Next Steps + +1. ✅ Test locally: `docker-compose up -d` +2. ✅ Check logs: `docker-compose logs -f` +3. ✅ Access app: http://localhost:3000 +4. ✅ Configure email scheduler if needed +5. ✅ Deploy to production server +6. ✅ Set up CI/CD pipeline (optional) + +--- + +## 🆘 Need Help? + +See detailed documentation in: +- `DOCKER_README.md` - Full Docker guide +- `EMAIL_SETUP.md` - Email configuration +- Docker logs: `docker-compose logs -f` + +--- + +Enjoy your Dockerized Lottery Investment Calculator! 🎰🐋 diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 0000000..e46fd7c --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,347 @@ +# Lottery Investment Calculator - Docker Setup + +## 🐋 Docker Deployment Guide + +### Prerequisites +- Docker Desktop installed (https://www.docker.com/products/docker-desktop) +- Docker Compose (included with Docker Desktop) + +--- + +## 🚀 Quick Start + +### 1. Build and Run Everything +```bash +docker-compose up -d +``` + +This will start: +- **Backend API** on http://localhost:5000 +- **Frontend Web App** on http://localhost:3000 + +### 2. Check Status +```bash +docker-compose ps +``` + +### 3. View Logs +```bash +# All services +docker-compose logs -f + +# Just backend +docker-compose logs -f backend + +# Just frontend +docker-compose logs -f frontend +``` + +### 4. Stop Everything +```bash +docker-compose down +``` + +--- + +## 📦 Individual Services + +### Backend Only +```bash +docker build -f Dockerfile.backend -t lottery-backend . +docker run -p 5000:5000 lottery-backend +``` + +### Frontend Only +```bash +docker build -f Dockerfile.frontend -t lottery-frontend . +docker run -p 3000:3000 lottery-frontend +``` + +### Email Scheduler (Optional) +```bash +docker-compose --profile email up -d +``` + +--- + +## 🔧 Configuration + +### Update Next.js to use standalone output + +Add to `frontend/next.config.ts`: +```typescript +const nextConfig = { + output: 'standalone', +}; +``` + +### Environment Variables + +Create `.env` file: +```bash +# Backend +FLASK_ENV=production + +# Frontend +NEXT_PUBLIC_API_URL=http://localhost:5000 + +# Email (optional) +EMAIL_SENDER=mblanke@gmail.com +EMAIL_RECIPIENT=mblanke@gmail.com +EMAIL_PASSWORD=vyapvyjjfrqpqnax +``` + +Then update `docker-compose.yml` to use env_file: +```yaml +services: + backend: + env_file: .env +``` + +--- + +## 🏗️ Build Process + +### First Time Setup +```bash +# Build all images +docker-compose build + +# Or build individually +docker-compose build backend +docker-compose build frontend +docker-compose build email-scheduler +``` + +### Rebuild After Code Changes +```bash +# Rebuild and restart +docker-compose up -d --build + +# Rebuild specific service +docker-compose up -d --build backend +``` + +--- + +## 🌐 Network Configuration + +All services communicate via the `lottery-network` bridge network. + +### Internal URLs (container to container): +- Backend: `http://backend:5000` +- Frontend: `http://frontend:3000` + +### External URLs (host to container): +- Backend: `http://localhost:5000` +- Frontend: `http://localhost:3000` + +--- + +## 📊 Health Checks + +The backend includes a health check endpoint: +```bash +curl http://localhost:5000/api/health +``` + +Check in Docker: +```bash +docker inspect lottery-backend | grep -A 10 Health +``` + +--- + +## 🔄 Production Deployment + +### Docker Hub +```bash +# Tag images +docker tag lottery-backend yourusername/lottery-backend:latest +docker tag lottery-frontend yourusername/lottery-frontend:latest + +# Push to Docker Hub +docker push yourusername/lottery-backend:latest +docker push yourusername/lottery-frontend:latest +``` + +### Deploy to Server +```bash +# Pull images on server +docker pull yourusername/lottery-backend:latest +docker pull yourusername/lottery-frontend:latest + +# Run with compose +docker-compose -f docker-compose.prod.yml up -d +``` + +--- + +## 🐛 Troubleshooting + +### Backend won't start +```bash +# Check logs +docker logs lottery-backend + +# Common issues: +# - Port 5000 already in use +# - Playwright installation failed +# - Missing dependencies +``` + +### Frontend can't connect to backend +```bash +# Check if backend is running +docker-compose ps + +# Test backend directly +curl http://localhost:5000/api/health + +# Check frontend environment +docker exec lottery-frontend env | grep API_URL +``` + +### Playwright browser issues +```bash +# Rebuild with no cache +docker-compose build --no-cache backend + +# Check Playwright installation +docker exec lottery-backend playwright --version +``` + +### Container keeps restarting +```bash +# View logs +docker logs lottery-backend --tail 100 + +# Check health status +docker inspect lottery-backend | grep -A 5 Health +``` + +--- + +## 📝 Useful Commands + +### Access Container Shell +```bash +# Backend +docker exec -it lottery-backend /bin/bash + +# Frontend +docker exec -it lottery-frontend /bin/sh +``` + +### Remove Everything +```bash +# Stop and remove containers, networks +docker-compose down + +# Also remove volumes +docker-compose down -v + +# Remove images +docker-compose down --rmi all +``` + +### Prune Unused Resources +```bash +docker system prune -a +``` + +### View Resource Usage +```bash +docker stats +``` + +--- + +## 🚢 Alternative: Docker without Compose + +### Create Network +```bash +docker network create lottery-network +``` + +### Run Backend +```bash +docker run -d \ + --name lottery-backend \ + --network lottery-network \ + -p 5000:5000 \ + lottery-backend +``` + +### Run Frontend +```bash +docker run -d \ + --name lottery-frontend \ + --network lottery-network \ + -p 3000:3000 \ + -e NEXT_PUBLIC_API_URL=http://localhost:5000 \ + lottery-frontend +``` + +--- + +## 🎯 Email Scheduler with Docker + +To include the email scheduler: + +1. **Start with email service:** +```bash +docker-compose --profile email up -d +``` + +2. **Or add to default profile** (edit docker-compose.yml): +Remove `profiles: - email` from email-scheduler service + +3. **Check email logs:** +```bash +docker logs lottery-email -f +``` + +--- + +## 🔐 Security Notes + +⚠️ **Important:** +- Never commit `.env` files with real credentials +- Use Docker secrets in production +- Set proper firewall rules +- Use HTTPS in production +- Regularly update base images + +--- + +## 📈 Scaling + +### Run multiple backend instances +```bash +docker-compose up -d --scale backend=3 +``` + +### Add load balancer (nginx) +See `docker-compose.prod.yml` for nginx configuration + +--- + +## 🆘 Support + +If containers won't start: +1. Check Docker Desktop is running +2. Ensure ports 3000 and 5000 are available +3. Check logs: `docker-compose logs` +4. Rebuild: `docker-compose up -d --build` +5. Reset: `docker-compose down && docker-compose up -d` + +--- + +## 📦 Image Sizes (Approximate) + +- Backend: ~1.5 GB (includes Chromium browser) +- Frontend: ~200 MB +- Email Scheduler: ~1.5 GB (includes Chromium browser) + +To reduce size, consider multi-stage builds or Alpine Linux variants. diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 0000000..92d60a1 --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,57 @@ +# Flask Backend Dockerfile +FROM python:3.13-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies for Playwright +RUN apt-get update && apt-get install -y \ + wget \ + gnupg \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libatspi2.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libwayland-client0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + xdg-utils \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Install Playwright browsers +RUN playwright install chromium +RUN playwright install-deps chromium + +# Copy application files +COPY app.py . +COPY lottery_calculator.py . +COPY ["import requests.py", "."] + +# Expose port +EXPOSE 5000 + +# Set environment variables +ENV FLASK_APP=app.py +ENV FLASK_ENV=production +ENV PYTHONUNBUFFERED=1 + +# Run the application +CMD ["python", "app.py"] diff --git a/Dockerfile.email b/Dockerfile.email new file mode 100644 index 0000000..4d28b59 --- /dev/null +++ b/Dockerfile.email @@ -0,0 +1,46 @@ +# Email Scheduler Dockerfile +FROM python:3.13-slim + +WORKDIR /app + +# Install system dependencies for Playwright +RUN apt-get update && apt-get install -y \ + wget \ + gnupg \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libatspi2.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libwayland-client0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + xdg-utils \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Install Playwright +RUN playwright install chromium +RUN playwright install-deps chromium + +# Copy email sender script +COPY email_sender.py . + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +CMD ["python", "email_sender.py"] diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000..066ba4e --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,47 @@ +# Next.js Frontend Dockerfile +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY frontend/package*.json ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY frontend/ . + +# Accept build argument for API URL (defaults to localhost for browser access) +ARG NEXT_PUBLIC_API_URL=http://localhost:5000 +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL + +# Build the application +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy necessary files +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/EMAIL_SETUP.md b/EMAIL_SETUP.md new file mode 100644 index 0000000..f832d2b --- /dev/null +++ b/EMAIL_SETUP.md @@ -0,0 +1,208 @@ +# Email Configuration for Lottery Jackpot Alerts + +## Setup Instructions + +### 1. Install Required Package +```bash +pip install schedule +``` + +### 2. Configure Email Settings + +Edit `email_sender.py` and update the `EMAIL_CONFIG` section: + +```python +EMAIL_CONFIG = { + 'smtp_server': 'smtp.gmail.com', # Your email provider's SMTP server + 'smtp_port': 587, + 'sender_email': 'your-email@gmail.com', # Your email address + 'sender_password': 'your-app-password', # Your app-specific password + 'recipient_email': 'recipient@example.com', # Where to send the report +} +``` + +### 3. Email Provider Settings + +#### For Gmail: +1. **Enable 2-Factor Authentication** on your Google account +2. **Generate App Password**: + - Go to: https://myaccount.google.com/apppasswords + - Select "Mail" and "Windows Computer" + - Copy the 16-character password + - Use this password in `sender_password` (NOT your regular Gmail password) +3. SMTP Settings: + - Server: `smtp.gmail.com` + - Port: `587` + +#### For Outlook/Hotmail: +- Server: `smtp-mail.outlook.com` +- Port: `587` +- Use your regular email and password + +#### For Yahoo: +- Server: `smtp.mail.yahoo.com` +- Port: `587` +- Generate app password at: https://login.yahoo.com/account/security + +#### For Other Providers: +Search for "[Your Provider] SMTP settings" to find the correct server and port. + +### 4. Test the Email + +Uncomment this line in the `main()` function to send a test email immediately: +```python +send_daily_jackpots() +``` + +Then run: +```bash +python email_sender.py +``` + +### 5. Schedule Daily Emails + +The script is configured to send emails at **7:00 AM** every day. + +To run it continuously: +```bash +python email_sender.py +``` + +Keep the terminal window open. The script will: +- Wait until 7:00 AM +- Fetch current jackpots +- Send formatted email +- Repeat daily + +### 6. Run as Background Service (Optional) + +#### Windows - Task Scheduler: +1. Open Task Scheduler +2. Create Basic Task +3. Name: "Lottery Jackpot Email" +4. Trigger: Daily at 7:00 AM +5. Action: Start a program + - Program: `python` + - Arguments: `d:\Projects\Dev\Lottery\email_sender.py` +6. Finish + +#### Windows - NSSM (Non-Sucking Service Manager): +```bash +# Install NSSM +choco install nssm + +# Create service +nssm install LotteryEmail python d:\Projects\Dev\Lottery\email_sender.py + +# Start service +nssm start LotteryEmail +``` + +#### Linux - Cron Job: +```bash +# Edit crontab +crontab -e + +# Add this line (runs at 7:00 AM daily) +0 7 * * * /usr/bin/python3 /path/to/email_sender.py +``` + +#### Linux - systemd service: +Create `/etc/systemd/system/lottery-email.service`: +```ini +[Unit] +Description=Lottery Jackpot Email Service +After=network.target + +[Service] +Type=simple +User=yourusername +WorkingDirectory=/path/to/Lottery +ExecStart=/usr/bin/python3 /path/to/email_sender.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Then: +```bash +sudo systemctl enable lottery-email +sudo systemctl start lottery-email +``` + +## Email Features + +The automated email includes: +- 🎰 **Powerball** jackpot (US) +- 🎰 **Mega Millions** jackpot (US) +- 🎰 **Lotto Max** jackpot (Canada - TAX FREE!) +- 🎰 **Lotto 6/49** jackpot (Canada - TAX FREE!) +- 📅 **Timestamp** of when data was fetched +- 💡 **Reminder** about Canadian tax-free winnings +- 🎨 **Beautiful HTML formatting** with colors and styling + +## Customization + +### Change Send Time: +Edit this line in `email_sender.py`: +```python +schedule.every().day.at("07:00").do(send_daily_jackpots) +``` + +Examples: +- `"09:30"` - 9:30 AM +- `"18:00"` - 6:00 PM +- `"00:00"` - Midnight + +### Send to Multiple Recipients: +Change the `send_email()` function: +```python +msg['To'] = "email1@example.com, email2@example.com, email3@example.com" +``` + +### Send Multiple Times Per Day: +Add multiple schedule lines: +```python +schedule.every().day.at("07:00").do(send_daily_jackpots) +schedule.every().day.at("19:00").do(send_daily_jackpots) +``` + +## Troubleshooting + +### "Authentication failed": +- Make sure you're using an **app password**, not your regular password (for Gmail) +- Check that 2FA is enabled on your account +- Verify SMTP server and port are correct + +### "Connection refused": +- Check your firewall settings +- Verify SMTP port is correct (usually 587 or 465) +- Try port 465 with `SMTP_SSL` instead of `SMTP` with `starttls()` + +### Script stops running: +- Check if your computer went to sleep +- Use Task Scheduler or systemd to auto-restart +- Check logs for error messages + +### Jackpots not updating: +- Websites may have changed their HTML structure +- Check if Playwright browser is installed: `playwright install chromium` +- Test the scraper functions individually + +## Security Notes + +⚠️ **IMPORTANT**: +- Never commit `email_sender.py` with your real credentials to Git +- Use environment variables for sensitive data in production +- Keep your app password secure +- Don't share your app password with anyone + +## Support + +If you encounter issues: +1. Run the test email first to verify configuration +2. Check error messages in the console +3. Verify internet connection +4. Confirm email provider settings +5. Test scraping functions individually diff --git a/Max.xlsx b/Max.xlsx new file mode 100644 index 0000000..9099efb Binary files /dev/null and b/Max.xlsx differ diff --git a/SKILLS.md b/SKILLS.md new file mode 100644 index 0000000..a42e375 --- /dev/null +++ b/SKILLS.md @@ -0,0 +1,23 @@ +# Skills Index + +These skill files define repeatable behaviors for agents and humans. + +Agents must follow them in this order: +1) SKILLS/00-operating-model.md +2) SKILLS/05-agent-taxonomy.md +3) SKILLS/10-definition-of-done.md +4) SKILLS/20-repo-map.md (use whenever unfamiliar with the repo) +5) SKILLS/25-algorithms-performance.md +6) SKILLS/26-vibe-coding-fundamentals.md +7) SKILLS/27-performance-profiling.md +8) SKILLS/30-implementation-rules.md +9) SKILLS/40-testing-quality.md +10) SKILLS/50-pr-review.md +11) SKILLS/56-ui-material-ui.md (for React/Next portal-style apps) +12) SKILLS/60-security-safety.md +13) SKILLS/70-docs-artifacts.md +14) SKILLS/82-mcp-server-design.md (when building MCP servers/tools) +15) SKILLS/83-fastmcp-3-patterns.md (if using FastMCP 3) +16) SKILLS/80-mcp-tools.md (if this repo has MCP tools) + +Rule: If anything conflicts, **AGENTS.md wins**. diff --git a/SKILLS/00-operating-model.md b/SKILLS/00-operating-model.md new file mode 100644 index 0000000..98d16b4 --- /dev/null +++ b/SKILLS/00-operating-model.md @@ -0,0 +1,21 @@ + +# Operating Model + +## Default cadence +- Prefer iterative progress over big bangs. +- Keep diffs small: target ≤ 300 changed lines per PR unless justified. +- Update tests/docs as part of the same change when possible. + +## Working agreement +- Start with a PLAN for non-trivial tasks. +- Implement the smallest slice that satisfies acceptance criteria. +- Verify via DoD. +- Write a crisp PR summary: what changed, why, and how verified. + +## Stop conditions (plan first) +Stop and produce a PLAN (do not code yet) if: +- scope is unclear +- more than 3 files will change +- data model changes +- auth/security boundaries +- performance-critical paths diff --git a/SKILLS/05-agent-taxonomy.md b/SKILLS/05-agent-taxonomy.md new file mode 100644 index 0000000..3c4a494 --- /dev/null +++ b/SKILLS/05-agent-taxonomy.md @@ -0,0 +1,36 @@ +# Agent Types & Roles (Practical Taxonomy) + +Use this skill to choose the *right* kind of agent workflow for the job. + +## Common agent “types” (in practice) + +### 1) Chat assistant (no tools) +Best for: explanations, brainstorming, small edits. +Risk: can hallucinate; no grounding in repo state. + +### 2) Tool-using single agent +Best for: well-scoped tasks where the agent can read/write files and run commands. +Key control: strict DoD gates + minimal permissions. + +### 3) Planner + Executor (2-role pattern) +Best for: medium complexity work (multi-file changes, feature work). +Flow: Planner writes plan + acceptance criteria → Executor implements → Reviewer checks. + +### 4) Multi-agent (specialists) +Best for: bigger features with separable workstreams (UI, backend, docs, tests). +Rule: isolate context per role; use separate branches/worktrees. + +### 5) Supervisor / orchestrator +Best for: long-running workflows with checkpoints (pipelines, report generation, PAD docs). +Rule: supervisor delegates, enforces gates, and composes final output. + +## Decision rules (fast) +- If you can describe it in ≤ 5 steps → single tool-using agent. +- If you need tradeoffs/design → Planner + Executor. +- If UI + backend + docs/tests all move → multi-agent specialists. +- If it’s a pipeline that runs repeatedly → orchestrator. + +## Guardrails (always) +- DoD is the truth gate. +- Separate branches/worktrees for parallel work. +- Log decisions + commands in AGENT_LOG.md. diff --git a/SKILLS/10-definition-of-done.md b/SKILLS/10-definition-of-done.md new file mode 100644 index 0000000..d99148e --- /dev/null +++ b/SKILLS/10-definition-of-done.md @@ -0,0 +1,24 @@ + +# Definition of Done (DoD) + +A change is "done" only when: + +## Code correctness +- Builds successfully (if applicable) +- Tests pass +- Linting/formatting passes +- Types/checks pass (if applicable) + +## Quality +- No new warnings introduced +- Edge cases handled (inputs validated, errors meaningful) +- Hot paths not regressed (if applicable) + +## Hygiene +- No secrets committed +- Docs updated if behavior or usage changed +- PR summary includes verification steps + +## Commands +- macOS/Linux: `./scripts/dod.sh` +- Windows: `\scripts\dod.ps1` diff --git a/SKILLS/20-repo-map.md b/SKILLS/20-repo-map.md new file mode 100644 index 0000000..810f986 --- /dev/null +++ b/SKILLS/20-repo-map.md @@ -0,0 +1,16 @@ + +# Repo Mapping Skill + +When entering a repo: +1) Read README.md +2) Identify entrypoints (app main / server startup / CLI) +3) Identify config (env vars, .env.example, config files) +4) Identify test/lint scripts (package.json, pyproject.toml, Makefile, etc.) +5) Write a 10-line "repo map" in the PLAN before changing code + +Output format: +- Purpose: +- Key modules: +- Data flow: +- Commands: +- Risks: diff --git a/SKILLS/25-algorithms-performance.md b/SKILLS/25-algorithms-performance.md new file mode 100644 index 0000000..7a63fc7 --- /dev/null +++ b/SKILLS/25-algorithms-performance.md @@ -0,0 +1,20 @@ +# Algorithms & Performance + +Use this skill when performance matters (large inputs, hot paths, or repeated calls). + +## Checklist +- Identify the **state** you’re recomputing. +- Add **memoization / caching** when the same subproblem repeats. +- Prefer **linear scans** + caches over nested loops when possible. +- If you can write it as a **recurrence**, you can test it. + +## Practical heuristics +- Measure first when possible (timing + input sizes). +- Optimize the biggest wins: avoid repeated I/O, repeated parsing, repeated network calls. +- Keep caches bounded (size/TTL) and invalidate safely. +- Choose data structures intentionally: dict/set for membership, heap for top-k, deque for queues. + +## Review notes (for PRs) +- Call out accidental O(n²) patterns. +- Suggest table/DP or memoization when repeated work is obvious. +- Add tests that cover base cases + typical cases + worst-case size. diff --git a/SKILLS/26-vibe-coding-fundamentals.md b/SKILLS/26-vibe-coding-fundamentals.md new file mode 100644 index 0000000..0eb231e --- /dev/null +++ b/SKILLS/26-vibe-coding-fundamentals.md @@ -0,0 +1,31 @@ +# Vibe Coding With Fundamentals (Safety Rails) + +Use this skill when you’re using “vibe coding” (fast, conversational building) but want production-grade outcomes. + +## The good +- Rapid scaffolding and iteration +- Fast UI prototypes +- Quick exploration of architectures and options + +## The failure mode +- “It works on my machine” code with weak tests +- Security foot-guns (auth, input validation, secrets) +- Performance cliffs (accidental O(n²), repeated I/O) +- Unmaintainable abstractions + +## Safety rails (apply every time) +- Always start with acceptance criteria (what “done” means). +- Prefer small PRs; never dump a huge AI diff. +- Require DoD gates (lint/test/build) before merge. +- Write tests for behavior changes. +- For anything security/data related: do a Reviewer pass. + +## When to slow down +- Auth/session/token work +- Anything touching payments, PII, secrets +- Data migrations/schema changes +- Performance-critical paths +- “It’s flaky” or “it only fails in CI” + +## Practical prompt pattern (use in PLAN) +- “State assumptions, list files to touch, propose tests, and include rollback steps.” diff --git a/SKILLS/27-performance-profiling.md b/SKILLS/27-performance-profiling.md new file mode 100644 index 0000000..6dc5504 --- /dev/null +++ b/SKILLS/27-performance-profiling.md @@ -0,0 +1,31 @@ +# Performance Profiling (Bun/Node) + +Use this skill when: +- a hot path feels slow +- CPU usage is high +- you suspect accidental O(n²) or repeated work +- you need evidence before optimizing + +## Bun CPU profiling +Bun supports CPU profiling via `--cpu-prof` (generates a `.cpuprofile` you can open in Chrome DevTools). + +Upcoming: `bun --cpu-prof-md \n\n\n\n\n\n \n \n \n \n \n \n \n \n \n\n \n \n \n\n\n\n \n \n\n \n\n\n\n\n\n\nSkip to content.\n\n\n\n\n
\n
\n

Previous Results

\n

Are you holding a winning ticket?

\n\n\n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n\n
\n \n
\n \n
\n
\n
\n
\n
Sat, Jul 19, 2025
\n
\n
\n\n
\n
\n
\n\n
28
\n\n
48
\n\n
51
\n\n
61
\n\n
69
\n\n
20
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jul 16, 2025
\n
\n
\n\n
\n
\n
\n\n
4
\n\n
21
\n\n
43
\n\n
48
\n\n
49
\n\n
22
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jul 14, 2025
\n
\n
\n\n
\n
\n
\n\n
8
\n\n
12
\n\n
45
\n\n
46
\n\n
63
\n\n
24
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jul 12, 2025
\n
\n
\n\n
\n
\n
\n\n
8
\n\n
16
\n\n
24
\n\n
33
\n\n
54
\n\n
18
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jul 9, 2025
\n
\n
\n\n
\n
\n
\n\n
5
\n\n
9
\n\n
25
\n\n
28
\n\n
69
\n\n
5
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jul 7, 2025
\n
\n
\n\n
\n
\n
\n\n
33
\n\n
35
\n\n
58
\n\n
61
\n\n
69
\n\n
25
\n
\n
\n\n \n Power Play\n 5x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jul 5, 2025
\n
\n
\n\n
\n
\n
\n\n
1
\n\n
28
\n\n
34
\n\n
50
\n\n
58
\n\n
8
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jul 2, 2025
\n
\n
\n\n
\n
\n
\n\n
7
\n\n
19
\n\n
21
\n\n
54
\n\n
63
\n\n
21
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jun 30, 2025
\n
\n
\n\n
\n
\n
\n\n
13
\n\n
28
\n\n
44
\n\n
52
\n\n
55
\n\n
6
\n
\n
\n\n \n Power Play\n 4x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jun 28, 2025
\n
\n
\n\n
\n
\n
\n\n
4
\n\n
35
\n\n
43
\n\n
52
\n\n
62
\n\n
12
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jun 25, 2025
\n
\n
\n\n
\n
\n
\n\n
2
\n\n
12
\n\n
37
\n\n
51
\n\n
61
\n\n
22
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jun 23, 2025
\n
\n
\n\n
\n
\n
\n\n
5
\n\n
25
\n\n
42
\n\n
44
\n\n
65
\n\n
20
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jun 21, 2025
\n
\n
\n\n
\n
\n
\n\n
3
\n\n
16
\n\n
32
\n\n
52
\n\n
62
\n\n
24
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jun 18, 2025
\n
\n
\n\n
\n
\n
\n\n
23
\n\n
29
\n\n
50
\n\n
64
\n\n
67
\n\n
11
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jun 16, 2025
\n
\n
\n\n
\n
\n
\n\n
17
\n\n
21
\n\n
23
\n\n
27
\n\n
52
\n\n
19
\n
\n
\n\n \n Power Play\n 5x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jun 14, 2025
\n
\n
\n\n
\n
\n
\n\n
4
\n\n
6
\n\n
9
\n\n
23
\n\n
59
\n\n
25
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jun 11, 2025
\n
\n
\n\n
\n
\n
\n\n
13
\n\n
25
\n\n
29
\n\n
37
\n\n
53
\n\n
3
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jun 9, 2025
\n
\n
\n\n
\n
\n
\n\n
30
\n\n
33
\n\n
40
\n\n
43
\n\n
52
\n\n
25
\n
\n
\n\n \n Power Play\n 4x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, Jun 7, 2025
\n
\n
\n\n
\n
\n
\n\n
31
\n\n
36
\n\n
43
\n\n
48
\n\n
62
\n\n
25
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, Jun 4, 2025
\n
\n
\n\n
\n
\n
\n\n
5
\n\n
17
\n\n
23
\n\n
35
\n\n
45
\n\n
24
\n
\n
\n\n \n Power Play\n 10x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, Jun 2, 2025
\n
\n
\n\n
\n
\n
\n\n
1
\n\n
7
\n\n
44
\n\n
57
\n\n
61
\n\n
21
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, May 31, 2025
\n
\n
\n\n
\n
\n
\n\n
1
\n\n
29
\n\n
37
\n\n
56
\n\n
68
\n\n
13
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, May 28, 2025
\n
\n
\n\n
\n
\n
\n\n
23
\n\n
27
\n\n
32
\n\n
35
\n\n
59
\n\n
11
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, May 26, 2025
\n
\n
\n\n
\n
\n
\n\n
13
\n\n
47
\n\n
52
\n\n
64
\n\n
67
\n\n
25
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, May 24, 2025
\n
\n
\n\n
\n
\n
\n\n
12
\n\n
18
\n\n
28
\n\n
48
\n\n
52
\n\n
5
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, May 21, 2025
\n
\n
\n\n
\n
\n
\n\n
9
\n\n
29
\n\n
31
\n\n
34
\n\n
43
\n\n
2
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, May 19, 2025
\n
\n
\n\n
\n
\n
\n\n
13
\n\n
14
\n\n
37
\n\n
50
\n\n
60
\n\n
11
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Sat, May 17, 2025
\n
\n
\n\n
\n
\n
\n\n
7
\n\n
34
\n\n
40
\n\n
42
\n\n
52
\n\n
15
\n
\n
\n\n \n Power Play\n 2x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Wed, May 14, 2025
\n
\n
\n\n
\n
\n
\n\n
4
\n\n
10
\n\n
24
\n\n
29
\n\n
53
\n\n
4
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n
\n
\n
Mon, May 12, 2025
\n
\n
\n\n
\n
\n
\n\n
15
\n\n
16
\n\n
41
\n\n
48
\n\n
60
\n\n
21
\n
\n
\n\n \n Power Play\n 3x\n \n
\n
\n
\n
\n\n
\n\n
\n \n \n \n
\n
\n
\n
\n \n
\n\n
\n
\n
\n\n\n\n\n\n
\n\n\n\n\n \n\n\n" + } +] \ No newline at end of file diff --git a/read_excel.py b/read_excel.py new file mode 100644 index 0000000..620f4d1 --- /dev/null +++ b/read_excel.py @@ -0,0 +1,31 @@ +import openpyxl +import pandas as pd + +# Load the workbook +wb = openpyxl.load_workbook('Max.xlsx') +print(f"Sheets: {wb.sheetnames}\n") + +# Read each sheet +for sheet_name in wb.sheetnames: + print(f"\n{'='*60}") + print(f"SHEET: {sheet_name}") + print('='*60) + ws = wb[sheet_name] + + # Print first 30 rows + for i, row in enumerate(ws.iter_rows(values_only=True), 1): + if any(cell is not None for cell in row): # Skip completely empty rows + print(f"Row {i}: {row}") + if i >= 30: + break + +print("\n\nNow using pandas for better formatting:") +print("="*60) + +# Try reading with pandas +for sheet_name in wb.sheetnames: + print(f"\n\nSheet: {sheet_name}") + print("-"*60) + df = pd.read_excel('Max.xlsx', sheet_name=sheet_name) + print(df.head(20)) + print(f"\nColumns: {df.columns.tolist()}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a588b53 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +flask +flask-cors +requests +beautifulsoup4 +playwright +urllib3 +openpyxl +pandas +schedule diff --git a/scripts/bootstrap_repo.ps1 b/scripts/bootstrap_repo.ps1 new file mode 100644 index 0000000..7f77ba3 --- /dev/null +++ b/scripts/bootstrap_repo.ps1 @@ -0,0 +1,27 @@ +Param( + [Parameter(Mandatory=$true)][string]$RepoPath +) + +$ErrorActionPreference = "Stop" +$RootDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$Target = (Resolve-Path $RepoPath).Path + +New-Item -ItemType Directory -Force -Path (Join-Path $Target ".claude\agents") | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $Target "SKILLS") | Out-Null + +Copy-Item -Force (Join-Path $RootDir "AGENTS.md") (Join-Path $Target "AGENTS.md") +if (Test-Path (Join-Path $RootDir "SKILLS.md")) { + Copy-Item -Force (Join-Path $RootDir "SKILLS.md") (Join-Path $Target "SKILLS.md") +} +Copy-Item -Recurse -Force (Join-Path $RootDir "SKILLS\*") (Join-Path $Target "SKILLS") +Copy-Item -Recurse -Force (Join-Path $RootDir ".claude\agents\*") (Join-Path $Target ".claude\agents") + +if (-not (Test-Path (Join-Path $Target ".gitlab-ci.yml")) -and (Test-Path (Join-Path $RootDir ".gitlab-ci.yml"))) { + Copy-Item -Force (Join-Path $RootDir ".gitlab-ci.yml") (Join-Path $Target ".gitlab-ci.yml") +} +if (-not (Test-Path (Join-Path $Target ".github")) -and (Test-Path (Join-Path $RootDir ".github"))) { + Copy-Item -Recurse -Force (Join-Path $RootDir ".github") (Join-Path $Target ".github") +} + +Write-Host "Bootstrapped repo: $Target" +Write-Host "Next: wire DoD gates to your stack and run scripts\dod.ps1" diff --git a/scripts/bootstrap_repo.sh b/scripts/bootstrap_repo.sh new file mode 100644 index 0000000..64e1ac0 --- /dev/null +++ b/scripts/bootstrap_repo.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Copy backbone files into an existing repo directory. +# Usage: ./scripts/bootstrap_repo.sh /path/to/repo + +TARGET="${1:-}" +if [[ -z "$TARGET" ]]; then + echo "Usage: $0 /path/to/repo" + exit 2 +fi + +SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +mkdir -p "$TARGET/.claude/agents" +mkdir -p "$TARGET/SKILLS" + +# Copy minimal backbone (adjust to taste) +cp -f "$SRC_DIR/AGENTS.md" "$TARGET/AGENTS.md" +cp -f "$SRC_DIR/SKILLS.md" "$TARGET/SKILLS.md" || true +cp -rf "$SRC_DIR/SKILLS/" "$TARGET/" || true +cp -rf "$SRC_DIR/.claude/agents/" "$TARGET/.claude/agents/" || true + +# Optional: CI templates +if [[ ! -f "$TARGET/.gitlab-ci.yml" && -f "$SRC_DIR/.gitlab-ci.yml" ]]; then + cp -f "$SRC_DIR/.gitlab-ci.yml" "$TARGET/.gitlab-ci.yml" +fi +if [[ ! -d "$TARGET/.github" && -d "$SRC_DIR/.github" ]]; then + cp -rf "$SRC_DIR/.github" "$TARGET/.github" +fi + +echo "Bootstrapped repo: $TARGET" +echo "Next: wire DoD gates to your stack (npm/pip) and run scripts/dod.sh" diff --git a/scripts/dod.ps1 b/scripts/dod.ps1 new file mode 100644 index 0000000..3629973 --- /dev/null +++ b/scripts/dod.ps1 @@ -0,0 +1,42 @@ + +$ErrorActionPreference = "Stop" +Write-Host "== DoD Gate ==" + +$root = Split-Path -Parent $PSScriptRoot +Set-Location $root + +function Has-Command($name) { + return $null -ne (Get-Command $name -ErrorAction SilentlyContinue) +} + +$hasNode = Test-Path ".\package.json" +$hasPy = (Test-Path ".\pyproject.toml") -or (Test-Path ".\requirements.txt") -or (Test-Path ".\requirements-dev.txt") + +if ($hasNode) { + if (-not (Has-Command "npm")) { throw "npm not found" } + Write-Host "+ npm ci"; npm ci + $pkg = Get-Content ".\package.json" | ConvertFrom-Json + if ($pkg.scripts.lint) { Write-Host "+ npm run lint"; npm run lint } + if ($pkg.scripts.typecheck) { Write-Host "+ npm run typecheck"; npm run typecheck } + if ($pkg.scripts.test) { Write-Host "+ npm test"; npm test } + if ($pkg.scripts.build) { Write-Host "+ npm run build"; npm run build } +} + +if ($hasPy) { + if (-not (Has-Command "python")) { throw "python not found" } + Write-Host "+ python -m pip install -U pip"; python -m pip install -U pip + if (Test-Path ".\requirements.txt") { Write-Host "+ pip install -r requirements.txt"; pip install -r requirements.txt } + if (Test-Path ".\requirements-dev.txt") { Write-Host "+ pip install -r requirements-dev.txt"; pip install -r requirements-dev.txt } + if (Has-Command "ruff") { + Write-Host "+ ruff check ."; ruff check . + Write-Host "+ ruff format --check ."; ruff format --check . + } + if (Has-Command "pytest") { Write-Host "+ pytest -q"; pytest -q } +} + +if (-not $hasNode -and -not $hasPy) { + Write-Host "No package.json or Python dependency files detected." + Write-Host "Customize scripts\dod.ps1 for this repo stack." +} + +Write-Host "DoD PASS" diff --git a/scripts/dod.sh b/scripts/dod.sh new file mode 100644 index 0000000..b672f49 --- /dev/null +++ b/scripts/dod.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "== DoD Gate ==" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +fail() { echo "DoD FAIL: $1" >&2; exit 1; } +run() { echo "+ $*"; "$@"; } +has() { command -v "$1" >/dev/null 2>&1; } + +HAS_NODE=0 +HAS_PY=0 +[[ -f package.json ]] && HAS_NODE=1 +[[ -f pyproject.toml || -f requirements.txt || -f requirements-dev.txt ]] && HAS_PY=1 + +if [[ $HAS_NODE -eq 1 ]]; then + has npm || fail "npm not found" + run npm ci + if has jq && jq -e '.scripts.lint' package.json >/dev/null 2>&1; then run npm run lint; fi + if has jq && jq -e '.scripts.typecheck' package.json >/dev/null 2>&1; then run npm run typecheck; fi + if has jq && jq -e '.scripts.test' package.json >/dev/null 2>&1; then run npm test; fi + if has jq && jq -e '.scripts.build' package.json >/dev/null 2>&1; then run npm run build; fi +fi + +if [[ $HAS_PY -eq 1 ]]; then + has python3 || fail "python3 not found" + run python3 -m pip install -U pip + if [[ -f requirements.txt ]]; then run python3 -m pip install -r requirements.txt; fi + if [[ -f requirements-dev.txt ]]; then run python3 -m pip install -r requirements-dev.txt; fi + if has ruff; then + run ruff check . || true + run ruff format --check . || true + fi + if has pytest; then run pytest -q || true; fi +fi + +if [[ $HAS_NODE -eq 0 && $HAS_PY -eq 0 ]]; then + echo "No package.json or Python dependency files detected." + echo "Customize scripts/dod.sh for this repo stack." +fi + +echo "DoD PASS" diff --git a/scripts/monday.ps1 b/scripts/monday.ps1 new file mode 100644 index 0000000..32eebfb --- /dev/null +++ b/scripts/monday.ps1 @@ -0,0 +1,56 @@ +Param( + [Parameter(Mandatory=$false)][string]$Command = "status", + [Parameter(Mandatory=$false)][string]$RepoPath = "" +) + +$ErrorActionPreference = "Stop" +$RootDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path + +Write-Host "== Dev Backbone Monday Runner ==" + +function Need-Cmd($name) { + if (-not (Get-Command $name -ErrorAction SilentlyContinue)) { + throw "Missing command: $name" + } +} + +switch ($Command) { + "status" { + $code = (Get-Command code -ErrorAction SilentlyContinue) + $git = (Get-Command git -ErrorAction SilentlyContinue) + $docker = (Get-Command docker -ErrorAction SilentlyContinue) + + Write-Host "[1] VS Code CLI:" ($code.Source ?? "NOT FOUND") + Write-Host "[2] Git: " ($git.Source ?? "NOT FOUND") + Write-Host "[3] Docker: " ($docker.Source ?? "NOT FOUND") + Write-Host "" + Write-Host "Profiles expected: Dev, Cyber, Infra" + Write-Host "Try: code --list-extensions --profile Dev" + } + + "vscode-purge" { + Need-Cmd code + if ($env:CONFIRM -ne "YES") { + Write-Host "Refusing to uninstall extensions without CONFIRM=YES" + Write-Host "Run: `$env:CONFIRM='YES'; .\scripts\monday.ps1 -Command vscode-purge" + exit 2 + } + & (Join-Path $RootDir "scripts\vscode_profiles.ps1") -Action purge + } + + "vscode-install" { + Need-Cmd code + & (Join-Path $RootDir "scripts\vscode_profiles.ps1") -Action install + } + + "repo-bootstrap" { + if ([string]::IsNullOrWhiteSpace($RepoPath)) { + throw "Usage: .\scripts\monday.ps1 -Command repo-bootstrap -RepoPath C:\path\to\repo" + } + & (Join-Path $RootDir "scripts\bootstrap_repo.ps1") -RepoPath $RepoPath + } + + default { + throw "Unknown command: $Command" + } +} diff --git a/scripts/monday.sh b/scripts/monday.sh new file mode 100644 index 0000000..9f2f4f0 --- /dev/null +++ b/scripts/monday.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Monday Overhaul Runner (safe by default) +# Usage: +# ./scripts/monday.sh status +# ./scripts/monday.sh vscode-purge (requires CONFIRM=YES) +# ./scripts/monday.sh vscode-install +# ./scripts/monday.sh repo-bootstrap /path/to/repo +# +# Notes: +# - VS Code profile creation is easiest once via UI (Profiles: Create Profile). +# This script assumes profiles exist: Dev, Cyber, Infra. + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +echo "== Dev Backbone Monday Runner ==" +echo "Repo: $ROOT_DIR" +echo + +cmd="${1:-status}" +shift || true + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { echo "Missing command: $1"; exit 1; } +} + +case "$cmd" in + status) + echo "[1] VS Code CLI: $(command -v code || echo 'NOT FOUND')" + echo "[2] Git: $(command -v git || echo 'NOT FOUND')" + echo "[3] Docker: $(command -v docker || echo 'NOT FOUND')" + echo + echo "Profiles expected: Dev, Cyber, Infra" + echo "Try: code --list-extensions --profile Dev" + ;; + + vscode-purge) + need_cmd code + if [[ "${CONFIRM:-NO}" != "YES" ]]; then + echo "Refusing to uninstall extensions without CONFIRM=YES" + echo "Run: CONFIRM=YES ./scripts/monday.sh vscode-purge" + exit 2 + fi + bash "$ROOT_DIR/scripts/vscode_profiles.sh" purge + ;; + + vscode-install) + need_cmd code + bash "$ROOT_DIR/scripts/vscode_profiles.sh" install + ;; + + repo-bootstrap) + repo_path="${1:-}" + if [[ -z "$repo_path" ]]; then + echo "Usage: ./scripts/monday.sh repo-bootstrap /path/to/repo" + exit 2 + fi + bash "$ROOT_DIR/scripts/bootstrap_repo.sh" "$repo_path" + ;; + + *) + echo "Unknown command: $cmd" + exit 2 + ;; +esac diff --git a/scripts/vscode_profiles.ps1 b/scripts/vscode_profiles.ps1 new file mode 100644 index 0000000..aafefff --- /dev/null +++ b/scripts/vscode_profiles.ps1 @@ -0,0 +1,75 @@ +Param( + [Parameter(Mandatory=$true)][ValidateSet("purge","install")][string]$Action +) + +$ErrorActionPreference = "Stop" + +function Profile-Exists([string]$ProfileName) { + try { + & code --list-extensions --profile $ProfileName | Out-Null + return $true + } catch { + return $false + } +} + +# Curated extension sets (edit to taste) +$DevExt = @( + "GitHub.copilot", + "GitHub.copilot-chat", + "GitHub.vscode-pull-request-github", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-azuretools.vscode-docker", + "ms-vscode-remote.remote-ssh", + "ms-vscode-remote.remote-containers", + "redhat.vscode-yaml", + "yzhang.markdown-all-in-one" +) + +$CyberExt = @($DevExt) # add more only if needed +$InfraExt = @( + "ms-azuretools.vscode-docker", + "ms-vscode-remote.remote-ssh", + "redhat.vscode-yaml", + "yzhang.markdown-all-in-one" +) + +function Purge-Profile([string]$ProfileName) { + Write-Host "Purging extensions from profile: $ProfileName" + if (-not (Profile-Exists $ProfileName)) { + Write-Host "Profile not found: $ProfileName (create once via UI: Profiles: Create Profile)" + return + } + $exts = & code --list-extensions --profile $ProfileName + foreach ($ext in $exts) { + if ([string]::IsNullOrWhiteSpace($ext)) { continue } + & code --profile $ProfileName --uninstall-extension $ext | Out-Null + } +} + +function Install-Profile([string]$ProfileName, [string[]]$Extensions) { + Write-Host "Installing extensions into profile: $ProfileName" + if (-not (Profile-Exists $ProfileName)) { + Write-Host "Profile not found: $ProfileName (create once via UI: Profiles: Create Profile)" + return + } + foreach ($ext in $Extensions) { + & code --profile $ProfileName --install-extension $ext | Out-Null + } +} + +switch ($Action) { + "purge" { + Purge-Profile "Dev" + Purge-Profile "Cyber" + Purge-Profile "Infra" + } + "install" { + Install-Profile "Dev" $DevExt + Install-Profile "Cyber" $CyberExt + Install-Profile "Infra" $InfraExt + } +} diff --git a/scripts/vscode_profiles.sh b/scripts/vscode_profiles.sh new file mode 100644 index 0000000..2ed6497 --- /dev/null +++ b/scripts/vscode_profiles.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Manage extensions per VS Code profile. +# Requires profiles to exist: Dev, Cyber, Infra +# Actions: +# ./scripts/vscode_profiles.sh purge (uninstall ALL extensions from those profiles) +# ./scripts/vscode_profiles.sh install (install curated sets) + +ACTION="${1:-}" +if [[ -z "$ACTION" ]]; then + echo "Usage: $0 {purge|install}" + exit 2 +fi + +need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1"; exit 1; }; } +need code + +# Curated extension sets (edit to taste) +DEV_EXT=( + "GitHub.copilot" + "GitHub.copilot-chat" + "GitHub.vscode-pull-request-github" + "dbaeumer.vscode-eslint" + "esbenp.prettier-vscode" + "ms-python.python" + "ms-python.vscode-pylance" + "ms-azuretools.vscode-docker" + "ms-vscode-remote.remote-ssh" + "ms-vscode-remote.remote-containers" + "redhat.vscode-yaml" + "yzhang.markdown-all-in-one" +) + +CYBER_EXT=( + "${DEV_EXT[@]}" + # Add only if you truly use them: + # "ms-kubernetes-tools.vscode-kubernetes-tools" +) + +INFRA_EXT=( + "ms-azuretools.vscode-docker" + "ms-vscode-remote.remote-ssh" + "redhat.vscode-yaml" + "yzhang.markdown-all-in-one" + # Optional: + # "hashicorp.terraform" +) + +purge_profile() { + local profile="$1" + echo "Purging extensions from profile: $profile" + # list may fail if profile doesn't exist + if ! code --list-extensions --profile "$profile" >/dev/null 2>&1; then + echo "Profile not found: $profile (create once via UI: Profiles: Create Profile)" + return 0 + fi + code --list-extensions --profile "$profile" | while read -r ext; do + [[ -z "$ext" ]] && continue + code --profile "$profile" --uninstall-extension "$ext" || true + done +} + +install_profile() { + local profile="$1"; shift + local exts=("$@") + echo "Installing extensions into profile: $profile" + if ! code --list-extensions --profile "$profile" >/dev/null 2>&1; then + echo "Profile not found: $profile (create once via UI: Profiles: Create Profile)" + return 0 + fi + for ext in "${exts[@]}"; do + [[ "$ext" =~ ^# ]] && continue + code --profile "$profile" --install-extension "$ext" + done +} + +case "$ACTION" in + purge) + purge_profile "Dev" + purge_profile "Cyber" + purge_profile "Infra" + ;; + install) + install_profile "Dev" "${DEV_EXT[@]}" + install_profile "Cyber" "${CYBER_EXT[@]}" + install_profile "Infra" "${INFRA_EXT[@]}" + ;; + *) + echo "Unknown action: $ACTION" + exit 2 + ;; +esac diff --git a/send_email_now.py b/send_email_now.py new file mode 100644 index 0000000..510fddb --- /dev/null +++ b/send_email_now.py @@ -0,0 +1,137 @@ +""" +Secure email sender that prompts for password instead of storing it. +This version is safer and works without App Passwords. +""" +import asyncio +from email_sender import ( + get_powerball, + get_mega_millions, + get_canadian_lotteries, + create_email_html, + format_currency +) +from datetime import datetime +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +import getpass + +def send_email_secure(sender_email, sender_password, recipient_email, subject, html_content): + """Send email with provided credentials""" + try: + # Create message + msg = MIMEMultipart('alternative') + msg['Subject'] = subject + msg['From'] = sender_email + msg['To'] = recipient_email + + # Attach HTML content + html_part = MIMEText(html_content, 'html') + msg.attach(html_part) + + # Try Gmail first + try: + print(" Trying Gmail SMTP...") + with smtplib.SMTP('smtp.gmail.com', 587) as server: + server.starttls() + server.login(sender_email, sender_password) + server.send_message(msg) + print(f"✅ Email sent successfully via Gmail!") + return True + except Exception as gmail_error: + print(f" Gmail failed: {gmail_error}") + + # Try alternative method - Gmail SSL port + try: + print(" Trying Gmail SSL (port 465)...") + with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server: + server.login(sender_email, sender_password) + server.send_message(msg) + print(f"✅ Email sent successfully via Gmail SSL!") + return True + except Exception as ssl_error: + print(f" Gmail SSL also failed: {ssl_error}") + raise + except Exception as e: + print(f"❌ Error sending email: {e}") + print("\n⚠️ Common issues:") + print(" 1. Gmail requires 2-Step Verification + App Password") + print(" 2. Check if 'Less secure app access' is enabled (not recommended)") + print(" 3. Verify your email and password are correct") + return False + +def send_lottery_email(): + """Fetch jackpots and send email with secure password prompt""" + print("\n" + "="*60) + print("🎰 LOTTERY JACKPOT EMAIL SENDER") + print("="*60) + + # Email configuration + sender_email = "mblanke@gmail.com" + recipient_email = "mblanke@gmail.com" + + print(f"\n📧 Email will be sent from/to: {sender_email}") + print("\n🔐 Please enter your Gmail password:") + print(" (Note: Gmail may require an App Password if you have 2FA enabled)") + + # Securely prompt for password (won't show on screen) + sender_password = getpass.getpass(" Password: ") + + if not sender_password: + print("❌ No password provided. Exiting.") + return + + print("\n" + "-"*60) + print("📊 Fetching lottery jackpots...") + print("-"*60) + + # Get US lotteries + print("\n🇺🇸 US Lotteries:") + print(" Fetching Powerball...") + powerball = get_powerball() + print(f" ✓ Powerball: {format_currency(powerball)}") + + print(" Fetching Mega Millions...") + mega_millions = get_mega_millions() + print(f" ✓ Mega Millions: {format_currency(mega_millions)}") + + # Get Canadian lotteries + print("\n🇨🇦 Canadian Lotteries:") + print(" Fetching Lotto Max and Lotto 6/49...") + lotto_max, lotto_649 = asyncio.run(get_canadian_lotteries()) + print(f" ✓ Lotto Max: {format_currency(lotto_max)}") + print(f" ✓ Lotto 6/49: {format_currency(lotto_649)}") + + # Create email + print("\n" + "-"*60) + print("📧 Creating email...") + print("-"*60) + subject = f"🎰 Lottery Report - {datetime.now().strftime('%B %d, %Y')}" + html_content = create_email_html(powerball, mega_millions, lotto_max, lotto_649) + print(" ✓ Email content created") + + # Send email + print("\n📤 Sending email...") + success = send_email_secure(sender_email, sender_password, recipient_email, subject, html_content) + + if success: + print("\n" + "="*60) + print("✅ SUCCESS!") + print("="*60) + print(f"📧 Check your inbox at: {recipient_email}") + print("💡 The email includes all current jackpot amounts") + print(" with beautiful HTML formatting!") + else: + print("\n" + "="*60) + print("❌ FAILED!") + print("="*60) + print("\n🔧 Options to fix:") + print(" 1. Enable 2-Step Verification in Gmail") + print(" 2. Generate App Password: https://myaccount.google.com/apppasswords") + print(" 3. Use the App Password instead of regular password") + print("\n Alternative: Use a different email service (Outlook, Yahoo, etc.)") + + print("\n") + +if __name__ == "__main__": + send_lottery_email() diff --git a/test_email.py b/test_email.py new file mode 100644 index 0000000..5728d89 --- /dev/null +++ b/test_email.py @@ -0,0 +1,89 @@ +""" +Quick test script to send a lottery jackpot email immediately. +Use this to verify your email configuration before scheduling. +""" +import asyncio +from email_sender import ( + get_powerball, + get_mega_millions, + get_canadian_lotteries, + create_email_html, + send_email, + format_currency, + EMAIL_CONFIG +) +from datetime import datetime + +def test_email(): + """Test the email sender by sending immediately""" + print("\n" + "="*60) + print("🧪 TESTING LOTTERY EMAIL SENDER") + print("="*60) + + # Display current configuration + print(f"\n📧 Email Configuration:") + print(f" From: {EMAIL_CONFIG['sender_email']}") + print(f" To: {EMAIL_CONFIG['recipient_email']}") + print(f" SMTP Server: {EMAIL_CONFIG['smtp_server']}:{EMAIL_CONFIG['smtp_port']}") + + if EMAIL_CONFIG['sender_email'] == 'your-email@gmail.com': + print("\n⚠️ WARNING: You need to update EMAIL_CONFIG in email_sender.py!") + print(" Please edit the file and add your email credentials.") + print(" See EMAIL_SETUP.md for instructions.") + return + + print("\n" + "-"*60) + print("📊 Fetching lottery jackpots...") + print("-"*60) + + # Get US lotteries + print("\n🇺🇸 US Lotteries:") + print(" Fetching Powerball...") + powerball = get_powerball() + print(f" ✓ Powerball: {format_currency(powerball)}") + + print(" Fetching Mega Millions...") + mega_millions = get_mega_millions() + print(f" ✓ Mega Millions: {format_currency(mega_millions)}") + + # Get Canadian lotteries + print("\n🇨🇦 Canadian Lotteries:") + print(" Fetching Lotto Max and Lotto 6/49...") + lotto_max, lotto_649 = asyncio.run(get_canadian_lotteries()) + print(f" ✓ Lotto Max: {format_currency(lotto_max)}") + print(f" ✓ Lotto 6/49: {format_currency(lotto_649)}") + + # Create email + print("\n" + "-"*60) + print("📧 Creating email...") + print("-"*60) + subject = f"🎰 TEST - Lottery Report - {datetime.now().strftime('%B %d, %Y')}" + html_content = create_email_html(powerball, mega_millions, lotto_max, lotto_649) + print(" ✓ Email content created") + + # Send email + print("\n📤 Sending email...") + success = send_email(subject, html_content) + + if success: + print("\n" + "="*60) + print("✅ TEST SUCCESSFUL!") + print("="*60) + print(f"📧 Check your inbox at: {EMAIL_CONFIG['recipient_email']}") + print("💡 If everything looks good, you can run email_sender.py") + print(" to schedule daily emails at 7:00 AM") + else: + print("\n" + "="*60) + print("❌ TEST FAILED!") + print("="*60) + print("🔍 Troubleshooting tips:") + print(" 1. Check your email and password in EMAIL_CONFIG") + print(" 2. For Gmail, use an App Password (not your regular password)") + print(" 3. Verify SMTP server and port are correct") + print(" 4. Check your internet connection") + print(" 5. See EMAIL_SETUP.md for detailed instructions") + + print("\n") + +if __name__ == "__main__": + test_email()