cctally 1.62.0 → 1.63.0
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_config.py +26 -0
- package/bin/_cctally_parser.py +15 -0
- package/bin/_cctally_statusline.py +9 -1
- package/bin/_lib_statusline.py +25 -13
- 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.63.0] - 2026-07-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `cctally statusline --usage-only` renders just the subscription usage chip (`5h X% · 7d Y%`), dropping the model, cost, burn-rate, context, and reset-countdown segments — for users who want a small quota indicator in their prompt without the cost telemetry. Persist it with `cctally config set statusline.usage_only true`; `--no-usage-only` forces the full statusline when the config key is on. Contributed by @nathanm4 (public PR #3).
|
|
12
|
+
|
|
8
13
|
## [1.62.0] - 2026-07-06
|
|
9
14
|
|
|
10
15
|
### Changed
|
package/bin/_cctally_config.py
CHANGED
|
@@ -309,6 +309,7 @@ ALLOWED_CONFIG_KEYS = (
|
|
|
309
309
|
"statusline.visual_burn_rate",
|
|
310
310
|
"statusline.cost_source",
|
|
311
311
|
"statusline.cctally_extensions",
|
|
312
|
+
"statusline.usage_only",
|
|
312
313
|
"budget.weekly_usd",
|
|
313
314
|
"budget.alerts_enabled",
|
|
314
315
|
"budget.alert_thresholds",
|
|
@@ -379,6 +380,25 @@ def _validate_statusline_cctally_extensions(value):
|
|
|
379
380
|
)
|
|
380
381
|
|
|
381
382
|
|
|
383
|
+
def _validate_statusline_usage_only(value):
|
|
384
|
+
"""Validate ``statusline.usage_only``.
|
|
385
|
+
|
|
386
|
+
Accepts booleans (preferred) or canonical truthy/falsy strings
|
|
387
|
+
(``true``/``false``/``yes``/``no``/``on``/``off``/``1``/``0``).
|
|
388
|
+
"""
|
|
389
|
+
if isinstance(value, bool):
|
|
390
|
+
return value
|
|
391
|
+
if isinstance(value, str):
|
|
392
|
+
lo = value.strip().lower()
|
|
393
|
+
if lo in ("true", "yes", "on", "1"):
|
|
394
|
+
return True
|
|
395
|
+
if lo in ("false", "no", "off", "0"):
|
|
396
|
+
return False
|
|
397
|
+
raise ValueError(
|
|
398
|
+
f"statusline.usage_only must be boolean (got {value!r})"
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
382
402
|
def cmd_config(args: argparse.Namespace) -> int:
|
|
383
403
|
"""Get/set/unset persisted user preferences in config.json.
|
|
384
404
|
|
|
@@ -544,6 +564,7 @@ def _config_known_value(config: dict, key: str) -> "object":
|
|
|
544
564
|
"statusline.visual_burn_rate",
|
|
545
565
|
"statusline.cost_source",
|
|
546
566
|
"statusline.cctally_extensions",
|
|
567
|
+
"statusline.usage_only",
|
|
547
568
|
):
|
|
548
569
|
sl_block = config.get("statusline") if isinstance(config, dict) else None
|
|
549
570
|
if not isinstance(sl_block, dict):
|
|
@@ -554,6 +575,7 @@ def _config_known_value(config: dict, key: str) -> "object":
|
|
|
554
575
|
"visual_burn_rate": "off",
|
|
555
576
|
"cost_source": "auto",
|
|
556
577
|
"cctally_extensions": True,
|
|
578
|
+
"usage_only": False,
|
|
557
579
|
}
|
|
558
580
|
if stored is None:
|
|
559
581
|
return defaults[inner]
|
|
@@ -561,6 +583,7 @@ def _config_known_value(config: dict, key: str) -> "object":
|
|
|
561
583
|
"visual_burn_rate": _validate_statusline_visual_burn_rate,
|
|
562
584
|
"cost_source": _validate_statusline_cost_source,
|
|
563
585
|
"cctally_extensions": _validate_statusline_cctally_extensions,
|
|
586
|
+
"usage_only": _validate_statusline_usage_only,
|
|
564
587
|
}[inner]
|
|
565
588
|
try:
|
|
566
589
|
return validator(stored)
|
|
@@ -987,12 +1010,14 @@ def _cmd_config_set(args: argparse.Namespace) -> int:
|
|
|
987
1010
|
"statusline.visual_burn_rate",
|
|
988
1011
|
"statusline.cost_source",
|
|
989
1012
|
"statusline.cctally_extensions",
|
|
1013
|
+
"statusline.usage_only",
|
|
990
1014
|
):
|
|
991
1015
|
inner_key = key.split(".", 1)[1]
|
|
992
1016
|
validator = {
|
|
993
1017
|
"visual_burn_rate": _validate_statusline_visual_burn_rate,
|
|
994
1018
|
"cost_source": _validate_statusline_cost_source,
|
|
995
1019
|
"cctally_extensions": _validate_statusline_cctally_extensions,
|
|
1020
|
+
"usage_only": _validate_statusline_usage_only,
|
|
996
1021
|
}[inner_key]
|
|
997
1022
|
try:
|
|
998
1023
|
normalized = validator(raw)
|
|
@@ -1390,6 +1415,7 @@ def _cmd_config_unset(args: argparse.Namespace) -> int:
|
|
|
1390
1415
|
"statusline.visual_burn_rate",
|
|
1391
1416
|
"statusline.cost_source",
|
|
1392
1417
|
"statusline.cctally_extensions",
|
|
1418
|
+
"statusline.usage_only",
|
|
1393
1419
|
):
|
|
1394
1420
|
inner_key = key.split(".", 1)[1]
|
|
1395
1421
|
with config_writer_lock():
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -868,6 +868,21 @@ def _build_statusline_parser(subparsers, name, *, help_text, xref):
|
|
|
868
868
|
action="store_false",
|
|
869
869
|
help="Suppress cctally 5h%%/7d%% segment.",
|
|
870
870
|
)
|
|
871
|
+
p.add_argument(
|
|
872
|
+
"--usage-only",
|
|
873
|
+
dest="usage_only",
|
|
874
|
+
action="store_true",
|
|
875
|
+
default=None,
|
|
876
|
+
help="Render only subscription usage percentages, e.g. "
|
|
877
|
+
"`5h 36%% · 7d 35%%` (config key statusline.usage_only).",
|
|
878
|
+
)
|
|
879
|
+
p.add_argument(
|
|
880
|
+
"--no-usage-only",
|
|
881
|
+
dest="usage_only",
|
|
882
|
+
action="store_false",
|
|
883
|
+
help="Render the full statusline even when statusline.usage_only "
|
|
884
|
+
"is enabled in config.",
|
|
885
|
+
)
|
|
871
886
|
p.add_argument(
|
|
872
887
|
"--config",
|
|
873
888
|
dest="config",
|
|
@@ -218,6 +218,14 @@ def cmd_statusline(args: argparse.Namespace) -> int:
|
|
|
218
218
|
)
|
|
219
219
|
ext_on = True
|
|
220
220
|
|
|
221
|
+
usage_only = _resolve(args.usage_only, "usage_only", False)
|
|
222
|
+
if not isinstance(usage_only, bool):
|
|
223
|
+
warn_once(
|
|
224
|
+
f"cctally statusline: invalid statusline.usage_only="
|
|
225
|
+
f"{usage_only!r}; using False"
|
|
226
|
+
)
|
|
227
|
+
usage_only = False
|
|
228
|
+
|
|
221
229
|
tz_name = _resolve_statusline_tz(getattr(args, "timezone", None), cfg, warn_once)
|
|
222
230
|
|
|
223
231
|
# Color: explicit CLI > NO_COLOR env > TTY detect.
|
|
@@ -232,6 +240,7 @@ def cmd_statusline(args: argparse.Namespace) -> int:
|
|
|
232
240
|
context_low_threshold=int(args.context_low_threshold),
|
|
233
241
|
context_medium_threshold=int(args.context_medium_threshold),
|
|
234
242
|
cctally_extensions=bool(ext_on),
|
|
243
|
+
usage_only=bool(usage_only),
|
|
235
244
|
color=bool(color),
|
|
236
245
|
display_tz_name=tz_name,
|
|
237
246
|
debug=bool(args.debug),
|
|
@@ -634,4 +643,3 @@ def _build_statusline_injections(warn_once):
|
|
|
634
643
|
warn_once=warn_once,
|
|
635
644
|
)
|
|
636
645
|
|
|
637
|
-
|
package/bin/_lib_statusline.py
CHANGED
|
@@ -54,6 +54,7 @@ class StatuslineArgs:
|
|
|
54
54
|
context_low_threshold: int
|
|
55
55
|
context_medium_threshold: int
|
|
56
56
|
cctally_extensions: bool
|
|
57
|
+
usage_only: bool
|
|
57
58
|
color: bool # ANSI on/off after auto-detect resolved
|
|
58
59
|
display_tz_name: str # IANA name; resolved upstream via
|
|
59
60
|
# get_display_tz_pref(cfg) — defaults to
|
|
@@ -372,6 +373,8 @@ def resolve_cctally_extensions(
|
|
|
372
373
|
inp: StatuslineInput,
|
|
373
374
|
now: datetime,
|
|
374
375
|
inj: StatuslineInjections,
|
|
376
|
+
*,
|
|
377
|
+
include_countdowns: bool = True,
|
|
375
378
|
) -> Optional[str]:
|
|
376
379
|
"""Segment 5 — cctally-only `5h X% (...) · 7d Y% (...)`.
|
|
377
380
|
|
|
@@ -411,12 +414,12 @@ def resolve_cctally_extensions(
|
|
|
411
414
|
parts = []
|
|
412
415
|
if five_pct is not None:
|
|
413
416
|
s = f"5h {int(round(five_pct))}%"
|
|
414
|
-
if five_resets is not None:
|
|
417
|
+
if include_countdowns and five_resets is not None:
|
|
415
418
|
s += f" ({_fmt_countdown(five_resets - now_epoch)})"
|
|
416
419
|
parts.append(s)
|
|
417
420
|
if seven_pct is not None:
|
|
418
421
|
s = f"7d {int(round(seven_pct))}%"
|
|
419
|
-
if seven_resets is not None:
|
|
422
|
+
if include_countdowns and seven_resets is not None:
|
|
420
423
|
s += f" ({_fmt_countdown(seven_resets - now_epoch)})"
|
|
421
424
|
parts.append(s)
|
|
422
425
|
return " · ".join(parts)
|
|
@@ -443,6 +446,19 @@ def _wrap_color(text: str, color: Optional[str], enable: bool) -> str:
|
|
|
443
446
|
_PERCENT_INT_RE = re.compile(r"(\d+)%")
|
|
444
447
|
|
|
445
448
|
|
|
449
|
+
def _colorize_usage_segment(ext: str, args: StatuslineArgs) -> str:
|
|
450
|
+
"""Color a 5h/7d usage segment using the cctally percent bands."""
|
|
451
|
+
nums = [int(x) for x in _PERCENT_INT_RE.findall(ext)]
|
|
452
|
+
mx = max(nums) if nums else 0
|
|
453
|
+
if mx < 60:
|
|
454
|
+
color = "green"
|
|
455
|
+
elif mx < 85:
|
|
456
|
+
color = "yellow"
|
|
457
|
+
else:
|
|
458
|
+
color = "red"
|
|
459
|
+
return _wrap_color(ext, color, args.color)
|
|
460
|
+
|
|
461
|
+
|
|
446
462
|
def render_statusline(
|
|
447
463
|
inp: StatuslineInput,
|
|
448
464
|
args: StatuslineArgs,
|
|
@@ -453,6 +469,12 @@ def render_statusline(
|
|
|
453
469
|
None segments (currently only segment 5). See spec §1 for the exact
|
|
454
470
|
layout and §3 for the data flow.
|
|
455
471
|
"""
|
|
472
|
+
if args.usage_only:
|
|
473
|
+
ext = resolve_cctally_extensions(
|
|
474
|
+
inp, now, inj, include_countdowns=False
|
|
475
|
+
)
|
|
476
|
+
return "" if ext is None else _colorize_usage_segment(ext, args)
|
|
477
|
+
|
|
456
478
|
seg1 = resolve_model_segment(inp)
|
|
457
479
|
|
|
458
480
|
# Segment 2: 💰 ... session / ... today / ... block (Xh Ym left)
|
|
@@ -486,17 +508,7 @@ def render_statusline(
|
|
|
486
508
|
if args.cctally_extensions:
|
|
487
509
|
ext = resolve_cctally_extensions(inp, now, inj)
|
|
488
510
|
if ext is not None:
|
|
489
|
-
|
|
490
|
-
# <60 green, <85 yellow, >=85 red.
|
|
491
|
-
nums = [int(x) for x in _PERCENT_INT_RE.findall(ext)]
|
|
492
|
-
mx = max(nums) if nums else 0
|
|
493
|
-
if mx < 60:
|
|
494
|
-
color = "green"
|
|
495
|
-
elif mx < 85:
|
|
496
|
-
color = "yellow"
|
|
497
|
-
else:
|
|
498
|
-
color = "red"
|
|
499
|
-
seg5 = _wrap_color(ext, color, args.color)
|
|
511
|
+
seg5 = _colorize_usage_segment(ext, args)
|
|
500
512
|
|
|
501
513
|
segs = [seg1, seg2, seg3, seg4]
|
|
502
514
|
if seg5 is not None:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.63.0",
|
|
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": {
|