Skip to content

Commit f7f8756

Browse files
committed
feat(github): improve rate limit messages with auth detection
- Add get_gh_cli_token() to use gh CLI if authenticated - Update get_github_rate_limit() to: - Try GITHUB_TOKEN env var first - Fall back to gh CLI token - Return authenticated and token_source fields - Add get_github_rate_limit_help() with: - Deep link to create PAT with pre-selected scopes - gh auth login instructions - Environment variable setup guide - Update audit.py to: - Show auth source: "(via gh CLI)" or "(via GITHUB_TOKEN)" - Display help when unauthenticated with 60/60 limit
1 parent 08e0b47 commit f7f8756

2 files changed

Lines changed: 148 additions & 8 deletions

File tree

audit.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from cli_audit.detection import audit_tool_installation # noqa: E402
2929
from cli_audit.snapshot import load_snapshot, write_snapshot, render_from_snapshot, get_snapshot_path # noqa: E402
3030
from cli_audit.render import render_table, print_summary, status_icon # noqa: E402
31-
from cli_audit.collectors import get_github_rate_limit # noqa: E402
31+
from cli_audit.collectors import get_github_rate_limit, get_github_rate_limit_help # noqa: E402
3232
from cli_audit import collectors # noqa: E402
3333
from cli_audit.logging_config import setup_logging # noqa: E402
3434
# Split file support (Phase 2.1)
@@ -353,10 +353,27 @@ def cmd_update(args: argparse.Namespace) -> int:
353353
if rate_limit:
354354
remaining = rate_limit.get("remaining", 0)
355355
limit = rate_limit.get("limit", 0)
356+
authenticated = rate_limit.get("authenticated", False)
357+
token_source = rate_limit.get("token_source", "")
358+
359+
# Build status message
360+
auth_info = ""
361+
if authenticated:
362+
if token_source == "gh_cli":
363+
auth_info = " (via gh CLI)"
364+
elif token_source == "GITHUB_TOKEN":
365+
auth_info = " (via GITHUB_TOKEN)"
366+
356367
if remaining < limit * 0.2: # Warn if less than 20% remaining
357-
print(f"⚠️ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
368+
print(f"⚠️ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
369+
if not authenticated:
370+
print(get_github_rate_limit_help(), file=sys.stderr)
371+
elif not authenticated and limit == 60:
372+
# Unauthenticated with default limit - suggest authentication
373+
print(f"ℹ️ GitHub rate limit: {remaining}/{limit} remaining (unauthenticated)", file=sys.stderr)
374+
print(get_github_rate_limit_help(), file=sys.stderr)
358375
else:
359-
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
376+
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
360377

361378
print(f"# Collecting fresh data for {total} tools...", file=sys.stderr)
362379
est_time = int((total / MAX_WORKERS) * 3 * 1.5)
@@ -487,10 +504,20 @@ def cmd_update(args: argparse.Namespace) -> int:
487504
if rate_limit:
488505
remaining = rate_limit.get("remaining", 0)
489506
limit = rate_limit.get("limit", 0)
490-
if remaining < limit * 0.2: # Warn if less than 20% remaining
491-
print(f"⚠️ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
507+
authenticated = rate_limit.get("authenticated", False)
508+
token_source = rate_limit.get("token_source", "")
509+
510+
auth_info = ""
511+
if authenticated:
512+
if token_source == "gh_cli":
513+
auth_info = " (via gh CLI)"
514+
elif token_source == "GITHUB_TOKEN":
515+
auth_info = " (via GITHUB_TOKEN)"
516+
517+
if remaining < limit * 0.2:
518+
print(f"⚠️ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
492519
else:
493-
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
520+
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
494521

495522
# Suggest package manager upgrades
496523
from cli_audit.catalog import suggest_package_manager_upgrades
@@ -626,7 +653,21 @@ def cmd_update_baseline(args: argparse.Namespace) -> int:
626653
if rate_limit:
627654
remaining = rate_limit.get("remaining", 0)
628655
limit = rate_limit.get("limit", 0)
629-
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
656+
authenticated = rate_limit.get("authenticated", False)
657+
token_source = rate_limit.get("token_source", "")
658+
659+
auth_info = ""
660+
if authenticated:
661+
if token_source == "gh_cli":
662+
auth_info = " (via gh CLI)"
663+
elif token_source == "GITHUB_TOKEN":
664+
auth_info = " (via GITHUB_TOKEN)"
665+
666+
if not authenticated and limit == 60:
667+
print(f"ℹ️ GitHub rate limit: {remaining}/{limit} remaining (unauthenticated)", file=sys.stderr)
668+
print(get_github_rate_limit_help(), file=sys.stderr)
669+
else:
670+
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
630671

631672
print(f"# Collecting upstream versions for {total} tools...", file=sys.stderr)
632673

@@ -671,7 +712,17 @@ def cmd_update_baseline(args: argparse.Namespace) -> int:
671712
if rate_limit:
672713
remaining = rate_limit.get("remaining", 0)
673714
limit = rate_limit.get("limit", 0)
674-
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining", file=sys.stderr)
715+
authenticated = rate_limit.get("authenticated", False)
716+
token_source = rate_limit.get("token_source", "")
717+
718+
auth_info = ""
719+
if authenticated:
720+
if token_source == "gh_cli":
721+
auth_info = " (via gh CLI)"
722+
elif token_source == "GITHUB_TOKEN":
723+
auth_info = " (via GITHUB_TOKEN)"
724+
725+
print(f"✓ GitHub rate limit: {remaining}/{limit} remaining{auth_info}", file=sys.stderr)
675726

676727
return 0
677728

cli_audit/collectors.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,44 @@ def version_key(v: str) -> tuple:
374374
return "", ""
375375

376376

377+
def get_gh_cli_token() -> str | None:
378+
"""Try to get GitHub token from gh CLI if authenticated.
379+
380+
Returns:
381+
Token string or None if gh CLI is not available/authenticated
382+
"""
383+
import shutil
384+
import subprocess
385+
386+
if not shutil.which("gh"):
387+
return None
388+
389+
try:
390+
# Check if gh is authenticated
391+
result = subprocess.run(
392+
["gh", "auth", "status"],
393+
capture_output=True,
394+
text=True,
395+
timeout=5,
396+
)
397+
if result.returncode != 0:
398+
return None
399+
400+
# Get the token
401+
result = subprocess.run(
402+
["gh", "auth", "token"],
403+
capture_output=True,
404+
text=True,
405+
timeout=5,
406+
)
407+
if result.returncode == 0 and result.stdout.strip():
408+
return result.stdout.strip()
409+
except Exception:
410+
pass
411+
412+
return None
413+
414+
377415
def get_github_rate_limit() -> dict[str, Any]:
378416
"""Get GitHub API rate limit status.
379417
@@ -384,7 +422,17 @@ def get_github_rate_limit() -> dict[str, Any]:
384422

385423
try:
386424
headers = {"User-Agent": "ai-cli-preparation/2.0"}
425+
426+
# Try to get token from multiple sources
387427
token = os.environ.get("GITHUB_TOKEN")
428+
token_source = "GITHUB_TOKEN" if token else None
429+
430+
# Try gh CLI if no token in environment
431+
if not token:
432+
token = get_gh_cli_token()
433+
if token:
434+
token_source = "gh_cli"
435+
388436
if token:
389437
headers["Authorization"] = f"token {token}"
390438

@@ -396,7 +444,48 @@ def get_github_rate_limit() -> dict[str, Any]:
396444
"remaining": core.get("remaining", 0),
397445
"used": core.get("used", 0),
398446
"reset": core.get("reset", 0),
447+
"authenticated": token is not None,
448+
"token_source": token_source,
399449
}
400450
except Exception as e:
401451
logger.debug(f"Failed to get GitHub rate limit: {e}")
402452
return {}
453+
454+
455+
def get_github_rate_limit_help() -> str:
456+
"""Get helpful instructions for setting up GitHub authentication.
457+
458+
Returns:
459+
Multiline string with instructions
460+
"""
461+
import shutil
462+
463+
lines = []
464+
465+
# Check if gh CLI is available
466+
has_gh = shutil.which("gh") is not None
467+
468+
lines.append("")
469+
lines.append("To increase GitHub API rate limit from 60 to 5,000 requests/hour:")
470+
lines.append("")
471+
472+
if has_gh:
473+
lines.append("Option 1 (Recommended): Use GitHub CLI (already installed)")
474+
lines.append(" gh auth login")
475+
lines.append("")
476+
lines.append("Option 2: Set GITHUB_TOKEN environment variable")
477+
else:
478+
lines.append("Option 1 (Recommended): Install and authenticate GitHub CLI")
479+
lines.append(" # Install: https://cli.github.com/")
480+
lines.append(" gh auth login")
481+
lines.append("")
482+
lines.append("Option 2: Set GITHUB_TOKEN environment variable")
483+
484+
# Deep link to create PAT with minimal required scopes
485+
# The 'public_repo' scope is needed for accessing public repository data
486+
pat_url = "https://github.com/settings/tokens/new?description=CLI%20Toolset&scopes=public_repo"
487+
lines.append(f" 1. Create a PAT: {pat_url}")
488+
lines.append(" 2. Add to your shell profile:")
489+
lines.append(" export GITHUB_TOKEN='ghp_your_token_here'")
490+
491+
return "\n".join(lines)

0 commit comments

Comments
 (0)