ccburn 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import platform
6
+ import subprocess
5
7
  from datetime import datetime, timezone
6
8
  from pathlib import Path
7
9
 
@@ -44,32 +46,81 @@ def get_credentials_path() -> Path:
44
46
  return Path.home() / ".claude" / ".credentials.json"
45
47
 
46
48
 
47
- def read_credentials() -> dict:
48
- """Read the raw credentials file.
49
+ def _read_credentials_from_keychain() -> dict | None:
50
+ """Read credentials from macOS Keychain.
49
51
 
50
52
  Returns:
51
- Parsed credentials dictionary
53
+ Parsed credentials dictionary or None if not found/failed.
54
+ """
55
+ try:
56
+ result = subprocess.run(
57
+ ["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
58
+ capture_output=True,
59
+ text=True,
60
+ check=True,
61
+ )
62
+ return json.loads(result.stdout.strip())
63
+ except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
64
+ return None
52
65
 
53
- Raises:
54
- CredentialsNotFoundError: If file doesn't exist
55
- InvalidCredentialsError: If file is malformed
66
+
67
+ def _read_credentials_from_file() -> dict | None:
68
+ """Read credentials from file.
69
+
70
+ Returns:
71
+ Parsed credentials dictionary or None if not found/failed.
56
72
  """
57
73
  creds_path = get_credentials_path()
58
74
 
59
75
  if not creds_path.exists():
60
- raise CredentialsNotFoundError(
61
- f"Credentials file not found at {creds_path}\n"
62
- "Please ensure Claude Code is installed and you are logged in.\n"
63
- "Run 'claude' to log in if needed."
64
- )
76
+ return None
65
77
 
66
78
  try:
67
79
  with open(creds_path) as f:
68
80
  return json.load(f)
69
- except json.JSONDecodeError as e:
70
- raise InvalidCredentialsError(f"Invalid JSON in credentials file: {e}") from e
71
- except PermissionError as e:
72
- raise CredentialsError(f"Permission denied reading {creds_path}") from e
81
+ except (json.JSONDecodeError, PermissionError):
82
+ return None
83
+
84
+
85
+ def read_credentials() -> dict:
86
+ """Read credentials from the appropriate storage.
87
+
88
+ On macOS, tries Keychain first, then falls back to file.
89
+ On Linux/Windows, reads from file.
90
+
91
+ Returns:
92
+ Parsed credentials dictionary
93
+
94
+ Raises:
95
+ CredentialsNotFoundError: If credentials not found
96
+ InvalidCredentialsError: If credentials are malformed
97
+ """
98
+ credentials = None
99
+
100
+ # On macOS, try Keychain first
101
+ if platform.system() == "Darwin":
102
+ credentials = _read_credentials_from_keychain()
103
+
104
+ # Fall back to file (or use file on non-macOS)
105
+ if credentials is None:
106
+ credentials = _read_credentials_from_file()
107
+
108
+ if credentials is None:
109
+ creds_path = get_credentials_path()
110
+ if platform.system() == "Darwin":
111
+ raise CredentialsNotFoundError(
112
+ "Credentials not found in macOS Keychain or file.\n"
113
+ "Please ensure Claude Code is installed and you are logged in.\n"
114
+ "Run 'claude' to log in if needed."
115
+ )
116
+ else:
117
+ raise CredentialsNotFoundError(
118
+ f"Credentials file not found at {creds_path}\n"
119
+ "Please ensure Claude Code is installed and you are logged in.\n"
120
+ "Run 'claude' to log in if needed."
121
+ )
122
+
123
+ return credentials
73
124
 
74
125
 
75
126
  def check_token_expired(credentials: dict) -> bool:
ccburn/display/gauges.py CHANGED
@@ -120,15 +120,9 @@ def create_gauge_section(
120
120
  complete_style=Style(color=usage_color), # Bright color for filled
121
121
  )
122
122
 
123
- # Usage label with emoji based on status
124
- usage_emoji = "📊"
125
- if limit_data.utilization >= 0.9:
126
- usage_emoji = "🚨"
127
- elif limit_data.utilization >= 0.75:
128
- usage_emoji = "⚠️"
129
-
123
+ # Usage label - keep emoji consistent (⚠️ has inconsistent width across terminals)
130
124
  usage_label = Text()
131
- usage_label.append(f"{usage_emoji} ", style="")
125
+ usage_label.append("📊 ", style="")
132
126
  usage_label.append("Usage", style=f"bold {usage_color}")
133
127
 
134
128
  table.add_row(
@@ -82,7 +82,8 @@ def format_reset_time(resets_at: datetime, now: datetime | None = None) -> str:
82
82
  # Convert to local time for display
83
83
  local_time = resets_at.astimezone()
84
84
  day_name = local_time.strftime("%a") # "Tue"
85
- time_str = local_time.strftime("%-I:%M %p") # "4:00 PM"
85
+ # Use %I and strip leading zero (%-I is Unix-only, %#I is Windows-only)
86
+ time_str = local_time.strftime("%I:%M %p").lstrip("0") # "4:00 PM"
86
87
  return f"Resets {day_name} {time_str}"
87
88
 
88
89
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccburn
3
- Version: 0.1.6
3
+ Version: 0.2.0
4
4
  Summary: Terminal-based Claude Code usage limit visualizer with real-time burn-up charts
5
5
  Author: JuanjoFuchs
6
6
  License-Expression: MIT
@@ -41,11 +41,12 @@ Dynamic: license-file
41
41
 
42
42
  [![CI](https://img.shields.io/github/actions/workflow/status/JuanjoFuchs/ccburn/ci.yml?branch=main&label=CI)](https://github.com/JuanjoFuchs/ccburn/actions/workflows/ci.yml)
43
43
  [![Release](https://img.shields.io/github/actions/workflow/status/JuanjoFuchs/ccburn/release.yml?label=Release)](https://github.com/JuanjoFuchs/ccburn/actions/workflows/release.yml)
44
+ [![npm](https://img.shields.io/npm/v/ccburn)](https://www.npmjs.com/package/ccburn)
44
45
  [![PyPI](https://img.shields.io/pypi/v/ccburn)](https://pypi.org/project/ccburn/)
45
46
  [![Python](https://img.shields.io/pypi/pyversions/ccburn)](https://pypi.org/project/ccburn/)
46
47
  [![GitHub Release](https://img.shields.io/github/v/release/JuanjoFuchs/ccburn)](https://github.com/JuanjoFuchs/ccburn/releases)
47
48
  [![WinGet](https://img.shields.io/badge/WinGet-pending-yellow)](https://github.com/microsoft/winget-pkgs/pulls?q=is%3Apr+ccburn)
48
- [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
49
+ [![License](https://img.shields.io/github/license/JuanjoFuchs/ccburn)](LICENSE)
49
50
 
50
51
  <p align="center">
51
52
  <img src="docs/cash1.png" alt="Burning tokens" width="140">
@@ -70,13 +71,27 @@ TUI and CLI for Claude Code usage limits — burn-up charts, compact mode for st
70
71
 
71
72
  ## Installation
72
73
 
73
- ### Windows (WinGet) *pending approval*
74
+ Run `claude` and login first to refresh credentials.
75
+
76
+ ### WinGet (*pending approval*)
74
77
 
75
78
  ```powershell
76
79
  winget install JuanjoFuchs.ccburn
77
80
  ```
78
81
 
79
- ### Cross-Platform (pip)
82
+ ### npx
83
+
84
+ ```bash
85
+ npx ccburn
86
+ ```
87
+
88
+ ### npm
89
+
90
+ ```bash
91
+ npm install -g ccburn
92
+ ```
93
+
94
+ ### pip
80
95
 
81
96
  ```bash
82
97
  pip install ccburn
@@ -92,7 +107,10 @@ pip install -e ".[dev]"
92
107
 
93
108
  ## Quick Start
94
109
 
95
- 1. **Ensure Claude Code is installed** ccburn reads credentials from Claude Code's config
110
+ 1. **Run Claude Code first** to ensure credentials are fresh:
111
+ ```bash
112
+ claude
113
+ ```
96
114
  2. **Run ccburn:**
97
115
  ```bash
98
116
  ccburn # Session limit (default)
@@ -3,20 +3,20 @@ ccburn/app.py,sha256=FcVHLbnygKbMTGIFZRN5jS_Pve8VdKImfLi2KsDuEoU,15510
3
3
  ccburn/cli.py,sha256=5qYYmOWcUXNYEdK-E3DBixfWQK4wKIQs6FPvTrIfOVI,7184
4
4
  ccburn/main.py,sha256=TqWLl9xxOtbpTQr-ObomzSLG3jNec2GZ6RKEQYYdGLg,2474
5
5
  ccburn/data/__init__.py,sha256=ZczEZwodQ-MMO5F7fVNsyIpUCRY8Ya9W4pwdOOJWxm4,803
6
- ccburn/data/credentials.py,sha256=PYrgzx-2NyiMTtIwBhulUdKawfT8cvWW4wIw2dOptks,3752
6
+ ccburn/data/credentials.py,sha256=wDiiTkZZDBjnYspvtWJ_52xXdTdIgBndLlfFMi-peZ8,5228
7
7
  ccburn/data/history.py,sha256=ouBxrXpMp_eTs0kba1Bg55TI6bsBSMToJ32tH1wNHQI,12879
8
8
  ccburn/data/models.py,sha256=Sd2T36gH6OaNHl9zRlnnQXI-ziBA8Gl6rPYQIzmr7G4,5403
9
9
  ccburn/data/usage_client.py,sha256=_dGwmI5vYPk4S-HUe2_fnTwSuAfTPaOFff7mKPFnhps,4570
10
10
  ccburn/display/__init__.py,sha256=aL7TV53kU5oxlIwJ8M17stG2aC6UeGB-pj2u5BOpegs,495
11
11
  ccburn/display/chart.py,sha256=j1qM6YX_K5SfKCxaob3-mT1vJjr8VMdsHIN8ORJu-2c,11421
12
- ccburn/display/gauges.py,sha256=jkPy3BuPsN1__4Ynt0DxK8t3Su_2jl5HAJifCmdtiiA,8696
12
+ ccburn/display/gauges.py,sha256=thxOOwjvg1cPcTq0wp7KzfGUzS77eLPNey2wnitciBw,8567
13
13
  ccburn/display/layout.py,sha256=UndPxyh32jWGdDgOZCvedz06WcKxYMSchLwpOkkXQKo,8093
14
14
  ccburn/utils/__init__.py,sha256=N6EzUX9hUJkuga_l9Ci3of1CWNtQgpNmMmNyY2DgYrg,1119
15
15
  ccburn/utils/calculator.py,sha256=QcFm5X-VWZzucHdInEjjqKV5oZaNsdpMgl8oKvHAQYc,6174
16
- ccburn/utils/formatting.py,sha256=vFpGY8v60G8OjTmZ1I1dYCr3h7YIfB8ezVMICb6RyZk,3327
17
- ccburn-0.1.6.dist-info/licenses/LICENSE,sha256=Qf2mqNi2qJ35JytfoTdR1SgYhZ2Mt4Ohcf-tu_MuYC0,1068
18
- ccburn-0.1.6.dist-info/METADATA,sha256=3xT-ILAAESqOdBig7Xnz2F90rqEwZxh__MrhSqOfALM,6787
19
- ccburn-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- ccburn-0.1.6.dist-info/entry_points.txt,sha256=GfFQ5VusMR8RJ9meygqWjaErdmYsf_arbILzf64WjLU,43
21
- ccburn-0.1.6.dist-info/top_level.txt,sha256=SM8TwGQZqQKKIQObVWQkfpA0OI4gRut7bPl-iM3g5RI,7
22
- ccburn-0.1.6.dist-info/RECORD,,
16
+ ccburn/utils/formatting.py,sha256=O6L3KoDEGdN4iBSXQFrO-ysk6i01Gp4veFXjeKqk0sc,3414
17
+ ccburn-0.2.0.dist-info/licenses/LICENSE,sha256=Qf2mqNi2qJ35JytfoTdR1SgYhZ2Mt4Ohcf-tu_MuYC0,1068
18
+ ccburn-0.2.0.dist-info/METADATA,sha256=mrI1KKGnyYkUubAy3xlZvRy60aZNoYgWzd9-kp7M5ec,6979
19
+ ccburn-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ ccburn-0.2.0.dist-info/entry_points.txt,sha256=GfFQ5VusMR8RJ9meygqWjaErdmYsf_arbILzf64WjLU,43
21
+ ccburn-0.2.0.dist-info/top_level.txt,sha256=SM8TwGQZqQKKIQObVWQkfpA0OI4gRut7bPl-iM3g5RI,7
22
+ ccburn-0.2.0.dist-info/RECORD,,
File without changes