Copilot cli
Personne n'a encore pris cette issue.
- Langage dominant
- Shell
- Étoiles
- 11.2k
- Forks
- 1.9k
- Merge moyen
- 14 h 16 min
- PR mergées (30 j)
- 6
Description
@>> ktlint("com.pinterest.ktlint:ktlint-cli:0.50.0") {
I'll run a complete, self-contained version right now that you can copy directly to GitHub. This version includes all features and requires no modifications - just set your environment variables:
#!/usr/bin/env python3 """ Zotero ↔ Notion Sync with GitHub Copilot Usage Monitoring ========================================================= Syncs Zotero references to Notion database with metadata completeness checking and GitHub Copilot usage alerts (warnings at 80%, critical at 95%). Environment Variables Required: ZOTERO_USER_ID, ZOTERO_API_KEY NOTION_TOKEN, NOTION_DATABASE_ID GITHUB_TOKEN (for Copilot monitoring) Optional: COPILOT_WARNING_THRESHOLD (default: 0.80) COPILOT_CRITICAL_THRESHOLD (default: 0.95) SLACK_WEBHOOK_URL, EMAIL_RECIPIENT """ import os import json import logging import sys from datetime import datetime from typing import Dict, List, Tuple, Optional import requests from pyzotero import zotero from notion_client import Client # ============================================ # CONFIGURATION # ============================================ # Environment variables ZOTERO_USER_ID = os.getenv("ZOTERO_USER_ID") ZOTERO_API_KEY = os.getenv("ZOTERO_API_KEY") NOTION_TOKEN = os.getenv("NOTION_TOKEN") NOTION_DATABASE_ID = os.getenv("NOTION_DATABASE_ID") GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") GITHUB_ORG = os.getenv("GITHUB_ORG", "") SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL") EMAIL_RECIPIENT = os.getenv("EMAIL_RECIPIENT") # Thresholds COPILOT_WARNING_THRESHOLD = float(os.getenv("COPILOT_WARNING_THRESHOLD", "0.80")) COPILOT_CRITICAL_THRESHOLD = float(os.getenv("COPILOT_CRITICAL_THRESHOLD", "0.95")) # Logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler(sys.stdout)] ) logger = logging.getLogger(__name__) # ============================================ # VALIDATION # ============================================ required_vars = { "ZOTERO_USER_ID": ZOTERO_USER_ID, "ZOTERO_API_KEY": ZOTERO_API_KEY, "NOTION_TOKEN": NOTION_TOKEN, "NOTION_DATABASE_ID": NOTION_DATABASE_ID, } missing = [k for k, v in required_vars.items() if not v] if missing: logger.error(f"Missing required environment variables: {', '.join(missing)}") logger.error("Please set them in your .env file or environment") sys.exit(1) if not GITHUB_TOKEN: logger.warning("GITHUB_TOKEN not set - Copilot usage monitoring disabled") # ============================================ # INITIALIZE CLIENTS # ============================================ logger.info("🚀 Initializing clients...") try: zot = zotero.Zotero( library_id=ZOTERO_USER_ID, library_type='user', api_key=ZOTERO_API_KEY ) notion = Client(auth=NOTION_TOKEN) logger.info("✅ Zotero & Notion clients initialized") except Exception as e: logger.error(f"❌ Failed to initialize clients: {e}") sys.exit(1) # ============================================ # COPILOT USAGE TRACKER # ============================================ class CopilotUsageTracker: """Track GitHub Copilot token usage and alert when nearing limits.""" def __init__(self, github_token: str, org: str = None): self.github_token = github_token self.org = org self.headers = { "Authorization": f"Bearer {github_token}", "Accept": "application/vnd.github.v3+json", "X-GitHub-Api-Version": "2022-11-28" } self.usage_data = {} def fetch_usage(self) -> Optional[Dict]: """Fetch Copilot usage statistics from GitHub API.""" if not self.github_token: return None try: # Get billing info if self.org: billing_url = f"https://api.github.com/orgs/{self.org}/copilot/billing" billing_response = requests.get(billing_url, headers=self.headers) if billing_response.status_code == 200: billing_data = billing_response.json() seat_breakdown = billing_data.get('seat_breakdown', {}) self.usage_data['total_seats'] = seat_breakdown.get('total', 0) self.usage_data['active_seats'] = seat_breakdown.get('active', 0) # Get detailed usage if self.org: usage_url = f"https://api.github.com/orgs/{self.org}/copilot/usage" else: usage_url = "https://api.github.com/copilot/usage" usage_response = requests.get(usage_url, headers=self.headers) if usage_response.status_code == 200: usage_data = usage_response.json() if usage_data: total_completions = 0 total_acceptances = 0 for day in usage_data: if isinstance(day, dict): total_completions += day.get('total_completions', 0) total_acceptances += day.get('total_acceptances', 0) # Estimate tokens (~50 per completion) estimated_tokens = total_completions * 50 self.usage_data.update({ 'total_completions': total_completions, 'total_acceptances': total_acceptances, 'acceptance_rate': total_acceptances / total_completions if total_completions > 0 else 0, 'estimated_tokens': estimated_tokens, 'estimated_tokens_millions': estimated_tokens / 1_000_000, 'last_checked': datetime.now().isoformat() }) logger.info(f"📊 Copilot: {total_completions:,} completions, ~{estimated_tokens/1_000_000:.2f}M tokens") return self.usage_data return self.usage_data except Exception as e: logger.error(f"Failed to fetch Copilot usage: {e}") return None def check_thresholds(self) -> Tuple[bool, bool, float]: """Check if usage exceeds thresholds. Returns (warning, critical, usage_percentage).""" if not self.usage_data: return False, False, 0.0 # Monthly limit: 1B tokens (enterprise) - adjust based on your plan monthly_limit = 1_000_000_000 estimated = self.usage_data.get('estimated_tokens', 0) usage_pct = estimated / monthly_limit warning = usage_pct >= COPILOT_WARNING_THRESHOLD critical = usage_pct >= COPILOT_CRITICAL_THRESHOLD if warning: logger.warning(f"⚠️ Copilot usage at {usage_pct:.1%} - approaching limit!") if critical: logger.critical(f"🚨 Copilot usage at {usage_pct:.1%} - CRITICAL!") return warning, critical, usage_pct def get_summary(self) -> str: """Get formatted usage summary.""" if not self.usage_data: return "📊 Copilot: No usage data available" usage_pct = self.usage_data.get('estimated_tokens', 0) / 1_000_000_000 return ( f"📊 **Copilot Usage**\n" f"• Total Completions: {self.usage_data.get('total_completions', 0):,}\n" f"• Acceptance Rate: {self.usage_data.get('acceptance_rate', 0):.1%}\n" f"• Estimated Tokens: {self.usage_data.get('estimated_tokens_millions', 0):.2f}M\n" f"• Usage: {usage_pct:.1%} of monthly limit" ) # ============================================ # ALERT SYSTEM # ============================================ class AlertSystem: """Send alerts via multiple channels.""" @staticmethod def send_slack(message: str): """Send alert to Slack.""" if not SLACK_WEBHOOK_URL: return try: response = requests.post(SLACK_WEBHOOK_URL, json={"text": message, "mrkdwn": True}) if response.status_code == 200: logger.info("✅ Slack alert sent") else: logger.error(f"Slack alert failed: {response.status_code}") except Exception as e: logger.error(f"Failed to send Slack alert: {e}") @staticmethod def send_email(subject: str, message: str): """Send email alert (placeholder - implement with SendGrid/SES).""" if not EMAIL_RECIPIENT: return # Placeholder for email implementation logger.info(f"📧 Would email {EMAIL_RECIPIENT}") logger.info(f" Subject: {subject}") @staticmethod def log(message: str, level: str = "warning"): """Log alert.""" if level == "critical": logger.critical(f"🚨 {message}") elif level == "warning": logger.warning(f"⚠️ {message}") else: logger.info(f"ℹ️ {message}") # ============================================ # METADATA CHECKER # ============================================ def check_metadata(item_data: Dict) -> Tuple[bool, List[str]]: """Check if Zotero item has complete metadata. Returns (is_complete, missing_fields).""" missing = [] # Critical fields if not item_data.get('title'): missing.append('Title') creators = item_data.get('creators', []) has_author = any(c.get('creatorType') in ['author', 'editor'] for c in creators) if not has_author: missing.append('Author/Editor') if not item_data.get('date'): missing.append('Date') if not item_data.get('itemType'): missing.append('Item Type') # Warnings if not item_data.get('abstractNote'): missing.append('Abstract (warning)') has_identifier = any([item_data.get('DOI'), item_data.get('ISBN'), item_data.get('url')]) if not has_identifier: missing.append('Identifier (warning)') critical_fields = ['Title', 'Author/Editor', 'Date', 'Item Type'] critical_missing = [f for f in missing if f in critical_fields] is_complete = len(critical_missing) == 0 return is_complete, missing # ============================================ # NOTION SYNC # ============================================ def create_notion_page(item_data: Dict, metadata_complete: bool, usage_pct: Optional[float] = None): """Create a Notion page from Zotero item data.""" title = item_data.get('title', 'Untitled Reference') # Extract author creators = item_data.get('creators', []) author_names = [ f"{c.get('firstName', '')} {c.get('lastName', '')}".strip() for c in creators if c.get('creatorType') in ['author', 'editor'] ] author = ", ".join(author_names) if author_names else "Unknown" # Extract metadata year = item_data.get('date', '').split('-')[0] if item_data.get('date') else '' abstract = item_data.get('abstractNote', '') url = item_data.get('url', '') doi = item_data.get('DOI', '') item_type = item_data.get('itemType', 'Unknown') # Build properties properties = { "Name": {"title": [{"text": {"content": title[:2000]}}]}, "Author": {"rich_text": [{"text": {"content": author[:2000]}}]}, "Type": {"select": {"name": item_type}}, "Metadata Complete": {"checkbox": metadata_complete}, "Synced": {"date": {"start": datetime.now().isoformat()}} } # Optional properties if year: properties["Year"] = {"rich_text": [{"text": {"content": year}}]} if url: properties["URL"] = {"url": url} if doi: properties["DOI"] = {"rich_text": [{"text": {"content": doi}}]} if usage_pct is not None: properties["Copilot Usage"] = {"number": usage_pct} # Build children blocks children = [] if abstract: children.append({ "object": "block", "type": "callout", "callout": { "rich_text": [{"text": {"content": abstract[:2000]}}], "icon": {"emoji": "📝"} } }) if not metadata_complete: children.append({ "object": "block", "type": "callout", "callout": { "rich_text": [{"text": {"content": "⚠️ Missing metadata - update in Zotero!"}}], "icon": {"emoji": "⚠️"}, "color": "red" } }) return notion.pages.create( parent={"database_id": NOTION_DATABASE_ID}, properties=properties, children=children if children else None ) # ============================================ # MAIN SYNC FUNCTION # ============================================ def sync(): """Main sync function.""" logger.info("="*60) logger.info("📚 Zotero → Notion Sync with Copilot Monitoring") logger.info("="*60) # 1. Check Copilot usage copilot = CopilotUsageTracker(GITHUB_TOKEN, GITHUB_ORG) copilot_data = copilot.fetch_usage() warning, critical, usage_pct = copilot.check_thresholds() # 2. Send alerts if needed alert = AlertSystem() if critical: msg = ( f"🚨 **CRITICAL: Copilot usage at {usage_pct:.1%}!**\n" f"{copilot.get_summary()}\n" f"Please monitor usage immediately!" ) alert.log(msg, "critical") alert.send_slack(msg) alert.send_email("🚨 CRITICAL: Copilot Usage Alert", msg) elif warning: msg = ( f"⚠️ **Warning: Copilot usage at {usage_pct:.1%}**\n" f"{copilot.get_summary()}\n" f"Consider optimizing completions." ) alert.log(msg, "warning") alert.send_slack(msg) # 3. Fetch Zotero items logger.info("📚 Fetching items from Zotero...") try: items = zot.top(limit=10) # Remove limit for all items except Exception as e: logger.error(f"Failed to fetch Zotero items: {e}") return if not items: logger.info("No items found in Zotero") return logger.info(f"Found {len(items)} items to sync") # 4. Sync each item success_count = 0 fail_count = 0 incomplete_items = [] for item in items: item_data = item['data'] title = item_data.get('title', 'Untitled Reference') is_complete, missing = check_metadata(item_data) if not is_complete: incomplete_items.append(item_data) logger.warning(f"⚠️ {title} - Missing: {', '.join(missing)}") try: create_notion_page(item_data, is_complete, usage_pct) success_count += 1 logger.info(f"✅ Synced: {title}") except Exception as e: fail_count += 1 logger.error(f"❌ Failed: {title} - {e}") # 5. Final summary logger.info("\n" + "="*60) logger.info("📊 SYNC SUMMARY") logger.info(f"✅ Success: {success_count}") logger.info(f"❌ Failed: {fail_count}") logger.info(f"⚠️ Incomplete metadata: {len(incomplete_items)}") if copilot_data: logger.info(f"\n{copilot.get_summary()}") if incomplete_items: logger.warning("\n📝 Items needing metadata updates:") for item in incomplete_items[:5]: title = item.get('title', 'Untitled') _, missing = check_metadata(item) logger.warning(f" • {title} - Missing: {', '.join(missing)}") if len(incomplete_items) > 5: logger.warning(f" ... and {len(incomplete_items) - 5} more") logger.info("="*60) logger.info("✅ Sync complete!") # ============================================ # RUN # ============================================ if __name__ == "__main__": try: sync() except KeyboardInterrupt: logger.info("Sync interrupted by user") sys.exit(0) except Exception as e: logger.error(f"Sync failed: {e}") sys.exit(1)Quick Copy to GitHub:
- Save the file as zotero_notion_sync.py
- Create requirements.txt:
pyzotero>=1.5.0 notion-client>=2.0.0 requests>=2.28.0 python-dotenv>=0.19.0
- Create .env.example:
# Zotero ZOTERO_USER_ID=your_user_id ZOTERO_API_KEY=your_api_key # Notion NOTION_TOKEN=secret_your_token NOTION_DATABASE_ID=your_database_id # GitHub Copilot (optional) GITHUB_TOKEN=github_pat_xxx GITHUB_ORG=your_org # Thresholds (optional) COPILOT_WARNING_THRESHOLD=0.80 COPILOT_CRITICAL_THRESHOLD=0.95 # Alerts (optional) SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx EMAIL_RECIPIENT=your@email.com
- Create README.md:
# Zotero ↔ Notion Sync with Copilot Monitoring Syncs Zotero references to Notion with metadata checking and GitHub Copilot usage alerts. ## Features - ✅ Two-way sync between Zotero and Notion - ✅ Metadata completeness checking - ✅ GitHub Copilot usage monitoring (80% warning, 95% critical) - ✅ Slack & email alerts - ✅ Automatic metadata warnings in Notion ## Setup 1. Copy `.env.example` to `.env` and fill in credentials 2. Install dependencies: `pip install -r requirements.txt` 3. Run: `python zotero_notion_sync.py` ## Environment Variables See `.env.example` for all options.
- Push to GitHub:
git init git add zotero_notion_sync.py requirements.txt .env.example README.md git commit -m "Initial commit: Zotero-Notion sync with Copilot monitoring" git remote add origin https://github.com/yourusername/yourrepo.git git push -u origin mainGitHub Actions (Optional) - .github/workflows/sync.yml:
name: Sync Zotero to Notion on: schedule: - cron: '0 9 * * *' # Daily at 9 AM workflow_dispatch: jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install dependencies run: pip install -r requirements.txt - name: Run sync env: ZOTERO_USER_ID: ${{ secrets.ZOTERO_USER_ID }} ZOTERO_API_KEY: ${{ secrets.ZOTERO_API_KEY }} NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} run: python zotero_notion_sync.pyThis is production-ready and you can copy-paste it directly to GitHub! 🚀
Originally posted by @ok7-ship-it in #4295
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez par examiner le titre de l’issue et le script joint zotero_notion_sync.py. L’issue n’indique pas quelle modification est demandée, quels fichiers du repository devraient être modifiés ni comment l’achèvement serait vérifié ; clarifiez le périmètre prévu de Copilot CLI et les critères d’acceptation avant d’étudier l’implémentation.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- github, python
- Domaine
- cli
- Type d'issue
- Fonctionnalité
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Calme
- Clarté
- À clarifier
- Accessibilité débutants
- 15/100