cctally 1.8.0 → 1.8.1
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.
- package/CHANGELOG.md +5 -0
- package/bin/cctally +34 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.8.1] - 2026-05-18
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `cctally five-hour-blocks` and `cctally five-hour-breakdown`: rows annotated with the `⚡` credit marker no longer push the table's right border one cell to the right of non-credit rows. `_boxed_table` was computing column widths via `len()` and padding via `str.ljust` / `str.rjust`, both of which count Unicode codepoints; `⚡` (U+26A1, `unicodedata.east_asian_width == "W"`) is one codepoint but renders two terminal cells, so the credit-prefixed Block Start cell (and the `⚡ CREDIT` divider row in the breakdown's Threshold column) under-padded by one cell and the right border drifted off-column on those rows only. New module-level `_display_width()` helper counts terminal cells (Wide/Fullwidth → 2, combining marks → 0, else 1) and `_boxed_table` now uses it for both width-max and padding. Byte-identical on the common case — any cell with no East Asian Wide / Fullwidth glyph renders unchanged, so existing pytest + cctally-test-all goldens stay green (1124 pytest + 1004 harness scenarios). Regressions: `tests/test_boxed_table_display_width.py` (⚡ in first column, ⚡ in inner column, ASCII no-op invariance).
|
|
12
|
+
|
|
8
13
|
## [1.8.0] - 2026-05-18
|
|
9
14
|
|
|
10
15
|
### Added
|
package/bin/cctally
CHANGED
|
@@ -80,6 +80,7 @@ import textwrap
|
|
|
80
80
|
import threading
|
|
81
81
|
import time
|
|
82
82
|
import traceback
|
|
83
|
+
import unicodedata
|
|
83
84
|
import urllib.error
|
|
84
85
|
import urllib.request
|
|
85
86
|
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
|
@@ -2691,6 +2692,29 @@ def _supports_unicode_stdout() -> bool:
|
|
|
2691
2692
|
return "UTF" in encoding
|
|
2692
2693
|
|
|
2693
2694
|
|
|
2695
|
+
def _display_width(s: str) -> int:
|
|
2696
|
+
"""Terminal cells consumed by ``s``.
|
|
2697
|
+
|
|
2698
|
+
Counts each codepoint by its East Asian Width: ``W`` / ``F`` (Wide
|
|
2699
|
+
/ Fullwidth) → 2 cells; combining marks → 0; everything else → 1.
|
|
2700
|
+
Ambiguous (``A``) defaults to 1, matching every non-CJK terminal
|
|
2701
|
+
locale — cctally has no CJK content in cell data, and `→` / `—` /
|
|
2702
|
+
`·` (all `A`) are intentionally rendered narrow.
|
|
2703
|
+
|
|
2704
|
+
Used by `_boxed_table` so cells containing wide glyphs (notably
|
|
2705
|
+
`⚡` U+26A1 on credit-row annotations) pad to the right cell count
|
|
2706
|
+
rather than the right codepoint count. Without this, `len()`-based
|
|
2707
|
+
padding under-pads by one cell per wide glyph and the right border
|
|
2708
|
+
drifts off-column on those rows only.
|
|
2709
|
+
"""
|
|
2710
|
+
width = 0
|
|
2711
|
+
for ch in s:
|
|
2712
|
+
if unicodedata.combining(ch):
|
|
2713
|
+
continue
|
|
2714
|
+
width += 2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1
|
|
2715
|
+
return width
|
|
2716
|
+
|
|
2717
|
+
|
|
2694
2718
|
def _boxed_table(
|
|
2695
2719
|
headers: list[str],
|
|
2696
2720
|
rows: list[list[str]],
|
|
@@ -2714,15 +2738,20 @@ def _boxed_table(
|
|
|
2714
2738
|
|
|
2715
2739
|
widths: list[int] = []
|
|
2716
2740
|
for idx, header in enumerate(headers):
|
|
2717
|
-
max_cell = max((
|
|
2718
|
-
widths.append(max(
|
|
2741
|
+
max_cell = max((_display_width(r[idx]) for r in sanitized_rows), default=0)
|
|
2742
|
+
widths.append(max(_display_width(header), max_cell))
|
|
2719
2743
|
|
|
2720
2744
|
def _pad(text: str, width: int, align: str) -> str:
|
|
2745
|
+
deficit = width - _display_width(text)
|
|
2746
|
+
if deficit <= 0:
|
|
2747
|
+
return text
|
|
2748
|
+
pad = " " * deficit
|
|
2721
2749
|
if align == "right":
|
|
2722
|
-
return text
|
|
2750
|
+
return pad + text
|
|
2723
2751
|
if align == "center":
|
|
2724
|
-
|
|
2725
|
-
|
|
2752
|
+
left = deficit // 2
|
|
2753
|
+
return (" " * left) + text + (" " * (deficit - left))
|
|
2754
|
+
return text + pad
|
|
2726
2755
|
|
|
2727
2756
|
if _supports_unicode_stdout():
|
|
2728
2757
|
chars = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
|
|
5
5
|
"homepage": "https://github.com/omrikais/cctally",
|
|
6
6
|
"repository": {
|