From 7524c29ce5ee1fc8ea9d0be7b8c37d24860d7557 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Mon, 9 Mar 2026 13:50:10 +0700 Subject: [PATCH] fix: Umami skill auto-load from unified .env - Load credentials from ~/.config/opencode/.env automatically - No need to configure .env in each skill directory - Fixes issue where new skills couldn't find credentials --- .DS_Store | Bin 6148 -> 6148 bytes skills/umami/scripts/umami_client.py | 89 +++++++++++---------------- 2 files changed, 35 insertions(+), 54 deletions(-) diff --git a/.DS_Store b/.DS_Store index c5608d8c345840dc93b422f2d8a619ff347ed3ec..33d1dc1b00452095c404a62204b86091486751e5 100644 GIT binary patch delta 15 WcmZoMXfc?uf8#+n_RXvu|M>wm5(Y&8 delta 17 YcmZoMXfc?upOJCnK{xizY#jgi0XmrmM*si- diff --git a/skills/umami/scripts/umami_client.py b/skills/umami/scripts/umami_client.py index 86e2ef6..5a936cd 100644 --- a/skills/umami/scripts/umami_client.py +++ b/skills/umami/scripts/umami_client.py @@ -4,6 +4,10 @@ Umami Analytics Client Self-hosted Umami integration with username/password authentication. Creates websites, gets tracking codes, and fetches analytics data. + +Automatically loads credentials from: +- ~/.config/opencode/.env (unified .env) +- .env (local, for development) """ import os @@ -13,36 +17,41 @@ import argparse from datetime import datetime, timedelta from typing import Dict, Optional, List from pathlib import Path +from dotenv import load_dotenv + +# Load credentials from unified .env +load_dotenv(os.path.expanduser('~/.config/opencode/.env')) +load_dotenv() # Also load local .env for development class UmamiClient: """Umami Analytics API client with username/password auth""" - def __init__(self, umami_url: str, username: str = None, password: str = None, token: str = None): + def __init__(self, umami_url: str = None, username: str = None, password: str = None, token: str = None): """ Initialize Umami client Args: - umami_url: Umami instance URL (e.g., https://analytics.example.com) - username: Umami username/email (for self-hosted) - password: Umami password (for self-hosted) + umami_url: Umami instance URL (loaded from .env if not provided) + username: Umami username/email (loaded from .env if not provided) + password: Umami password (loaded from .env if not provided) token: Bearer token (optional, if already have) """ - self.umami_url = umami_url.rstrip('/') - self.api_url = f"{self.umami_url}/api" - self.username = username - self.password = password + # Load from environment if not provided + self.umami_url = umami_url or os.getenv('UMAMI_URL', '') + self.username = username or os.getenv('UMAMI_USERNAME', '') + self.password = password or os.getenv('UMAMI_PASSWORD', '') self.token = token self.user_id = None # Auto-login if credentials provided - if username and password and not token: + if self.username and self.password and not token: self.login() def login(self) -> Dict: """Login to Umami and get bearer token""" try: - url = f"{self.api_url}/auth/login" + url = f"{self.umami_url}/api/auth/login" data = { 'username': self.username, 'password': self.password @@ -81,18 +90,9 @@ class UmamiClient: } def create_website(self, name: str, domain: str) -> Dict: - """ - Create new Umami website - - Args: - name: Website name - domain: Website domain (full URL) - - Returns: - Website creation result - """ + """Create new Umami website""" try: - url = f"{self.api_url}/websites" + url = f"{self.umami_url}/api/websites" data = { 'name': name, 'domain': domain @@ -129,7 +129,7 @@ class UmamiClient: def list_websites(self) -> List[Dict]: """Get all websites""" try: - url = f"{self.api_url}/websites" + url = f"{self.umami_url}/api/websites" response = requests.get(url, headers=self._get_headers()) response.raise_for_status() result = response.json() @@ -147,21 +147,12 @@ class UmamiClient: return [] def get_stats(self, website_id: str, days: int = 30) -> Dict: - """ - Get website statistics - - Args: - website_id: Umami website ID - days: Number of days to look back - - Returns: - Analytics stats - """ + """Get website statistics""" try: end_date = datetime.now() start_date = end_date - timedelta(days=days) - url = f"{self.api_url}/websites/{website_id}/stats" + url = f"{self.umami_url}/api/websites/{website_id}/stats" params = { 'startAt': int(start_date.timestamp() * 1000), 'endAt': int(end_date.timestamp() * 1000) @@ -191,16 +182,7 @@ class UmamiClient: return f'' def add_tracking_to_astro(self, website_repo: str, website_id: str) -> Dict: - """ - Add Umami tracking to Astro website - - Args: - website_repo: Path to Astro website repo - website_id: Umami website ID - - Returns: - Result of adding tracking - """ + """Add Umami tracking to Astro website""" try: tracking_script = self._get_tracking_script(website_id) @@ -219,7 +201,6 @@ class UmamiClient: break if not layout_file: - # Try to find any .astro file in src/layouts layouts_dir = os.path.join(website_repo, 'src/layouts') if os.path.exists(layouts_dir): for f in os.listdir(layouts_dir): @@ -230,18 +211,14 @@ class UmamiClient: if not layout_file: return {'success': False, 'error': 'No Astro layout file found'} - # Read layout file with open(layout_file, 'r', encoding='utf-8') as f: content = f.read() - # Add tracking before if '' in content: content = content.replace('', f' {tracking_script}\n ') else: - # If no , add at end of file content += f'\n{tracking_script}\n' - # Write back with open(layout_file, 'w', encoding='utf-8') as f: f.write(content) @@ -261,9 +238,9 @@ def main(): parser.add_argument('--action', required=True, choices=['create-website', 'get-tracking', 'add-tracking', 'get-stats', 'list-websites']) - parser.add_argument('--umami-url', required=True, help='Umami instance URL') - parser.add_argument('--username', help='Umami username') - parser.add_argument('--password', help='Umami password') + parser.add_argument('--umami-url', help='Umami instance URL (or set UMAMI_URL in .env)') + parser.add_argument('--username', help='Umami username (or set UMAMI_USERNAME in .env)') + parser.add_argument('--password', help='Umami password (or set UMAMI_PASSWORD in .env)') parser.add_argument('--website-name', help='Website name (for create)') parser.add_argument('--website-domain', help='Website domain (for create/find)') parser.add_argument('--website-id', help='Website ID (for stats)') @@ -273,10 +250,14 @@ def main(): args = parser.parse_args() print(f"\nšŸ“Š Umami Analytics Client") - print(f"URL: {args.umami_url}\n") + print(f"URL: {args.umami_url or os.getenv('UMAMI_URL', 'Not set')}\n") - # Initialize client - client = UmamiClient(args.umami_url, args.username, args.password) + # Initialize client (loads credentials from .env automatically) + client = UmamiClient( + umami_url=args.umami_url, + username=args.username, + password=args.password + ) if args.action == 'create-website': if not args.website_name or not args.website_domain: