gemini-cli-usage 0.1.0__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-cli-usage
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Summary: Gemini CLI quota monitor — fetches Code Assist quota data from Google's backend
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gemini-cli-usage"
3
- version = "0.1.0"
3
+ version = "0.1.3"
4
4
  description = "Gemini CLI quota monitor — fetches Code Assist quota data from Google's backend"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -30,10 +30,56 @@ import urllib.request
30
30
  from datetime import UTC, datetime
31
31
  from pathlib import Path
32
32
 
33
- GEMINI_DIR = Path.home() / ".gemini"
34
- OAUTH_FILE = GEMINI_DIR / "oauth_creds.json"
35
- SETTINGS_FILE = GEMINI_DIR / "settings.json"
36
- DEFAULT_USAGE_FILE = GEMINI_DIR / "usage-limits.json"
33
+ _NATIVE_GEMINI_DIR = Path.home() / ".gemini"
34
+
35
+
36
+ def _wsl_gemini_dirs() -> list[Path]:
37
+ """Return candidate .gemini dirs from WSL distros (Windows only)."""
38
+ if sys.platform != "win32":
39
+ return []
40
+ import subprocess
41
+
42
+ try:
43
+ result = subprocess.run(
44
+ ["wsl", "-l", "-q"],
45
+ capture_output=True,
46
+ timeout=5,
47
+ )
48
+ raw = result.stdout.decode("utf-16-le", errors="replace")
49
+ distros = [d.strip() for d in raw.splitlines() if d.strip()]
50
+ except Exception:
51
+ return []
52
+
53
+ dirs: list[Path] = []
54
+ for distro in distros:
55
+ wsl_home = Path(f"//wsl$/{distro}/home")
56
+ try:
57
+ if wsl_home.is_dir():
58
+ for user_dir in wsl_home.iterdir():
59
+ candidate = user_dir / ".gemini"
60
+ if candidate.is_dir():
61
+ dirs.append(candidate)
62
+ except OSError:
63
+ continue
64
+ return dirs
65
+
66
+
67
+ def _find_gemini_file(filename: str) -> Path:
68
+ """Return the first existing .gemini/<filename> from native + WSL paths."""
69
+ native = _NATIVE_GEMINI_DIR / filename
70
+ if native.exists():
71
+ return native
72
+ for wsl_dir in _wsl_gemini_dirs():
73
+ candidate = wsl_dir / filename
74
+ if candidate.exists():
75
+ return candidate
76
+ return native # default even if missing
77
+
78
+
79
+ GEMINI_DIR = _NATIVE_GEMINI_DIR
80
+ OAUTH_FILE = _find_gemini_file("oauth_creds.json")
81
+ SETTINGS_FILE = _find_gemini_file("settings.json")
82
+ DEFAULT_USAGE_FILE = _find_gemini_file("usage-limits.json")
37
83
 
38
84
  DAEMON_INTERVAL = 300 # 5 minutes
39
85
  CACHE_MAX_AGE = 300
@@ -53,6 +99,14 @@ AUTH_LABELS = {
53
99
  OAUTH_CLIENT_ID_PATTERN = re.compile(r"const OAUTH_CLIENT_ID = '([^']+)';")
54
100
  OAUTH_CLIENT_SECRET_PATTERN = re.compile(r"const OAUTH_CLIENT_SECRET = '([^']+)';")
55
101
 
102
+ # ANSI color codes — disabled when stdout is not a terminal (e.g. piped or redirected).
103
+ _TTY = sys.stdout.isatty()
104
+ _RED = "\033[0;31m" if _TTY else ""
105
+ _YELLOW = "\033[0;33m" if _TTY else ""
106
+ _GREEN = "\033[0;32m" if _TTY else ""
107
+ _DIM = "\033[0;90m" if _TTY else ""
108
+ _RESET = "\033[0m" if _TTY else ""
109
+
56
110
 
57
111
  def _read_json(path: Path) -> dict | list | None:
58
112
  try:
@@ -409,12 +463,8 @@ def _color_pct(pct: float | int | None) -> str:
409
463
  if pct is None:
410
464
  return "?"
411
465
  p = float(pct)
412
- red = "\033[0;31m"
413
- yellow = "\033[0;33m"
414
- green = "\033[0;32m"
415
- reset = "\033[0m"
416
- color = red if p >= 70 else yellow if p >= 40 else green
417
- return f"{color}{_format_pct(p)}{reset}"
466
+ color = _RED if p >= 70 else _YELLOW if p >= 40 else _GREEN
467
+ return f"{color}{_format_pct(p)}{_RESET}"
418
468
 
419
469
 
420
470
  def _format_pct(pct: float | int | None) -> str:
@@ -427,9 +477,6 @@ def _format_pct(pct: float | int | None) -> str:
427
477
 
428
478
 
429
479
  def _print_status(data: dict):
430
- dim = "\033[0;90m"
431
- reset = "\033[0m"
432
-
433
480
  print(f"Project: {Path(data['project_root']).name}")
434
481
  print(f"Auth: {data.get('auth_label', 'unknown')}")
435
482
 
@@ -454,14 +501,14 @@ def _print_status(data: dict):
454
501
  )
455
502
  print(
456
503
  f" {label:{name_width}s} {_color_pct(bucket.get('used_pct'))} used"
457
- f"{remain_part}{dim}{reset_part}{reset}"
504
+ f"{remain_part}{_DIM}{reset_part}{_RESET}"
458
505
  )
459
506
 
460
507
  for bucket in quota.get("buckets", []):
461
508
  model = bucket.get("model") or "unknown"
462
509
  print_bucket_line(model, bucket)
463
510
  elif data.get("quota_error"):
464
- print(f" {'Quota':20s} {dim}{data['quota_error']}{reset}")
511
+ print(f" {'Quota':20s} {_DIM}{data['quota_error']}{_RESET}")
465
512
 
466
513
 
467
514
  def _statusline_text(data: dict) -> str:
@@ -520,7 +567,8 @@ def cmd_json(args):
520
567
 
521
568
  def cmd_daemon(args):
522
569
  signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
523
- signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
570
+ if hasattr(signal, "SIGTERM"):
571
+ signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
524
572
 
525
573
  root = Path(args.root).resolve() if args.root else None
526
574
  usage_file = get_usage_file()