aiuse 3.2.4__tar.gz → 3.2.5__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.
Files changed (96) hide show
  1. {aiuse-3.2.4/src/aiuse.egg-info → aiuse-3.2.5}/PKG-INFO +1 -1
  2. {aiuse-3.2.4 → aiuse-3.2.5}/pyproject.toml +1 -1
  3. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/__init__.py +1 -1
  4. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/report.py +20 -22
  5. {aiuse-3.2.4 → aiuse-3.2.5/src/aiuse.egg-info}/PKG-INFO +1 -1
  6. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_report.py +82 -0
  7. {aiuse-3.2.4 → aiuse-3.2.5}/LICENSE +0 -0
  8. {aiuse-3.2.4 → aiuse-3.2.5}/README.md +0 -0
  9. {aiuse-3.2.4 → aiuse-3.2.5}/setup.cfg +0 -0
  10. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/__main__.py +0 -0
  11. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/agent_notes.py +0 -0
  12. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/ai_stub.py +0 -0
  13. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/__init__.py +0 -0
  14. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/history.py +0 -0
  15. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/local_runtimes.py +0 -0
  16. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/pace.py +0 -0
  17. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/selfdescribe.py +0 -0
  18. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/suggest.py +0 -0
  19. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/use_or_lose.py +0 -0
  20. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/attribute.py +0 -0
  21. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/chat_format.py +0 -0
  22. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/cli.py +0 -0
  23. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/__init__.py +0 -0
  24. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/bailian.py +0 -0
  25. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/base.py +0 -0
  26. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/caut.py +0 -0
  27. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/clinepass.py +0 -0
  28. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/codexbar.py +0 -0
  29. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/cswap.py +0 -0
  30. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/grok.py +0 -0
  31. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/hermes.py +0 -0
  32. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/muse.py +0 -0
  33. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/opencode_go.py +0 -0
  34. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/opencode_zen.py +0 -0
  35. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openrouter.py +0 -0
  36. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openusage.py +0 -0
  37. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openusage_sh.py +0 -0
  38. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/qwencloud.py +0 -0
  39. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/runner.py +0 -0
  40. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/tokscale.py +0 -0
  41. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/config.py +0 -0
  42. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/credentials.py +0 -0
  43. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/ledger.py +0 -0
  44. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/macos_trust.py +0 -0
  45. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/models.py +0 -0
  46. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/sampler.py +0 -0
  47. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/secretspec.py +0 -0
  48. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/serve.py +0 -0
  49. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tty.py +0 -0
  50. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/__init__.py +0 -0
  51. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/app.py +0 -0
  52. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/builders.py +0 -0
  53. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/watch.py +0 -0
  54. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/SOURCES.txt +0 -0
  55. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/dependency_links.txt +0 -0
  56. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/entry_points.txt +0 -0
  57. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/requires.txt +0 -0
  58. {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/top_level.txt +0 -0
  59. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_attribution.py +0 -0
  60. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_bailian.py +0 -0
  61. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_base_run_json.py +0 -0
  62. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_caut_parse.py +0 -0
  63. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_chat_format.py +0 -0
  64. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_cli.py +0 -0
  65. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_clinepass.py +0 -0
  66. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_codexbar_parse.py +0 -0
  67. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_config.py +0 -0
  68. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_credentials.py +0 -0
  69. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_cswap_parse.py +0 -0
  70. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_grok.py +0 -0
  71. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_hermes_parse.py +0 -0
  72. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_history.py +0 -0
  73. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_install_deps.py +0 -0
  74. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_local_runtimes.py +0 -0
  75. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_macos_trust.py +0 -0
  76. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_models_cli_binary.py +0 -0
  77. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_models_pace.py +0 -0
  78. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_muse.py +0 -0
  79. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_opencode_go.py +0 -0
  80. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_opencode_zen.py +0 -0
  81. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_openrouter.py +0 -0
  82. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_openusage_parse.py +0 -0
  83. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_pace.py +0 -0
  84. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_qwencloud.py +0 -0
  85. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_release_script.py +0 -0
  86. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_runner_consolidation.py +0 -0
  87. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_selfdescribe.py +0 -0
  88. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_serve.py +0 -0
  89. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_shared_quota_semantics.py +0 -0
  90. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_snapshots_persistence.py +0 -0
  91. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_suggest.py +0 -0
  92. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tokscale_parse.py +0 -0
  93. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tty.py +0 -0
  94. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tui.py +0 -0
  95. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_use_or_lose.py +0 -0
  96. {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_watch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiuse
3
- Version: 3.2.4
3
+ Version: 3.2.5
4
4
  Summary: Aggregate AI subscription/API usage and flag use-it-or-lose-it allotments
5
5
  Author: djbclark
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aiuse"
7
- version = "3.2.4"
7
+ version = "3.2.5"
8
8
  description = "Aggregate AI subscription/API usage and flag use-it-or-lose-it allotments"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """Aggregate provider quotas and flag use-it-or-lose-it allotments."""
2
2
 
3
- __version__ = "3.2.4"
3
+ __version__ = "3.2.5"
@@ -1398,6 +1398,25 @@ def _cell_color(s: _Style, used: float) -> Any:
1398
1398
  return s.green
1399
1399
 
1400
1400
 
1401
+ def _style_clock_percent(s: _Style, cell: _ClockCell, *, width: int) -> str:
1402
+ """Right-align ``NNu/NNl`` into ``width`` and color only the left token.
1403
+
1404
+ The used side (``44u/``) stays the default color. The left side (``56l``
1405
+ or ``56l+``) is one colored token, so the ``l`` does not drop out between
1406
+ the number and the reset span. An inferred clock is dim end to end.
1407
+ Visible width is always ``width``: a short pair is padded on the left,
1408
+ and the color codes are not part of that width, so the next column starts
1409
+ on the same character in every row.
1410
+ """
1411
+ padded = f"{_clock_percent_text(cell):>{width}}"
1412
+ if cell.inferred:
1413
+ return s.dim(padded)
1414
+ slash = padded.rfind("/")
1415
+ if slash < 0:
1416
+ return _cell_color(s, cell.used_percent)(padded)
1417
+ return padded[: slash + 1] + _cell_color(s, cell.used_percent)(padded[slash + 1 :])
1418
+
1419
+
1401
1420
  def render_clock_matrix(
1402
1421
  alerts: list[UseOrLoseAlert],
1403
1422
  *,
@@ -1511,33 +1530,12 @@ def render_clock_matrix(
1511
1530
  continue
1512
1531
  any_inferred = any_inferred or cell.inferred
1513
1532
  any_folded = any_folded or bool(cell.folded)
1514
- percent = _clock_percent_text(cell)
1515
1533
  span = _format_reset_span(
1516
1534
  cell.days_until_reset,
1517
1535
  estimated=cell.reset_estimated,
1518
1536
  compact=layout.compact_deadline,
1519
1537
  )
1520
- # Split percent into used part (before "/") and left part (after "/" but before "l")
1521
- # Format: "44u/56l" or "44u/56l+" if folded
1522
- # Keep used part and slash in normal text, color only the left part
1523
- if "/" in percent:
1524
- used_part, rest = percent.split("/", 1)
1525
- # rest is like "56l" or "56l+"
1526
- left_part = rest.rstrip("l+")
1527
- mark = "+" if "+" in rest else ""
1528
- if cell.inferred:
1529
- colored_used = s.dim(used_part)
1530
- colored_left = s.dim(left_part)
1531
- else:
1532
- colored_used = used_part # Normal text for used part
1533
- colored_left = _cell_color(s, cell.used_percent)(left_part)
1534
- colored = f"{colored_used}/{colored_left}l{mark}"
1535
- else:
1536
- colored = (
1537
- s.dim(f"{percent:>{w_pct}}")
1538
- if cell.inferred
1539
- else _cell_color(s, cell.used_percent)(f"{percent:>{w_pct}}")
1540
- )
1538
+ colored = _style_clock_percent(s, cell, width=w_pct)
1541
1539
  if span is None:
1542
1540
  tail.append(colored + (" " * (w_clock - w_pct)))
1543
1541
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiuse
3
- Version: 3.2.4
3
+ Version: 3.2.5
4
4
  Summary: Aggregate AI subscription/API usage and flag use-it-or-lose-it allotments
5
5
  Author: djbclark
6
6
  License-Expression: MIT
@@ -1916,6 +1916,88 @@ def test_clock_matrix_omits_slash_when_clock_has_no_timestamp():
1916
1916
  assert "0u/100l" in tokens
1917
1917
 
1918
1918
 
1919
+ def test_clock_matrix_pads_percents_and_colors_only_the_left_token():
1920
+ """A short ``4u/96l`` must not slide the next clock left of a ``79u/21l``.
1921
+
1922
+ Color wraps the whole left token (``21l``), not just the digits, and the
1923
+ used side stays plain. Stripping ANSI leaves the plain table unchanged.
1924
+ """
1925
+ now = utcnow()
1926
+
1927
+ def window(label: str, used: float, left: float, *, window_minutes: int, **when: float) -> QuotaWindow:
1928
+ return QuotaWindow(
1929
+ label=label,
1930
+ used_percent=used,
1931
+ remaining_percent=left,
1932
+ resets_at=now + timedelta(**when),
1933
+ window_minutes=window_minutes,
1934
+ )
1935
+
1936
+ snap = Snapshot(
1937
+ collected_at=now,
1938
+ accounts=[
1939
+ AccountUsage(
1940
+ provider="claude",
1941
+ source="cswap",
1942
+ account="me@gmail.com",
1943
+ billing_kind=BillingKind.SUBSCRIPTION_WINDOW,
1944
+ windows=[
1945
+ window("5-hour", 79.0, 21.0, window_minutes=300, hours=1, minutes=52),
1946
+ window("weekly", 56.0, 44.0, window_minutes=10080, days=5, hours=8),
1947
+ ],
1948
+ ),
1949
+ AccountUsage(
1950
+ provider="cursor",
1951
+ source="codexbar",
1952
+ account="me@gmail.com",
1953
+ billing_kind=BillingKind.SUBSCRIPTION_WINDOW,
1954
+ windows=[
1955
+ window("5-hour", 4.0, 96.0, window_minutes=300, minutes=36),
1956
+ window("weekly", 29.0, 71.0, window_minutes=10080, days=3, hours=2),
1957
+ ],
1958
+ ),
1959
+ ],
1960
+ )
1961
+ plain = render_clock_matrix([], snapshot=snap, color=False, width=160)
1962
+ colored = render_clock_matrix([], snapshot=snap, color=True, width=160)
1963
+ assert _strip_ansi(colored) == plain
1964
+
1965
+ rows = {line.split()[2]: line for line in plain.splitlines() if line[:5].strip() in _BAND_TAGS}
1966
+ claude = rows["claude"]
1967
+ cursor = rows["cursor"]
1968
+ assert claude.index("56u/44l") == cursor.index("29u/71l")
1969
+ assert claude.index("79u/21l") == cursor.index("4u/96l") - 1
1970
+ header = plain.splitlines()[0]
1971
+ assert header.index("5H") + 1 == claude.index("79u/21l") + len("79u/21l") - 1
1972
+ assert header.index("WEEK") + 3 == claude.index("56u/44l") + len("56u/44l") - 1
1973
+
1974
+ # First data row has no zebra stripe, so the reset is a plain ``[0m``.
1975
+ # The next row's stripe rewrites that reset; the open code is what matters.
1976
+ assert "79u/\033[33m21l\033[0m" in colored
1977
+ assert "\033[32m96l" in colored
1978
+ assert "\033[33m79u" not in colored
1979
+ assert "\033[32m4u" not in colored
1980
+
1981
+
1982
+ def test_style_clock_percent_pads_and_keeps_fold_mark_in_the_left_token():
1983
+ from aiuse.report import _ClockCell, _style_clock_percent
1984
+
1985
+ on = _Style(True)
1986
+ short = _style_clock_percent(on, _ClockCell(used_percent=4.0), width=8)
1987
+ assert _strip_ansi(short) == " 4u/96l"
1988
+ assert short == " 4u/\033[32m96l\033[0m"
1989
+
1990
+ folded = _style_clock_percent(on, _ClockCell(used_percent=56.0, folded=2), width=8)
1991
+ assert _strip_ansi(folded) == "56u/44l+"
1992
+ assert folded == "56u/\033[36m44l+\033[0m"
1993
+
1994
+ inferred = _style_clock_percent(on, _ClockCell(used_percent=4.0, inferred=True), width=8)
1995
+ assert inferred == on.dim(" 4u/96l")
1996
+
1997
+ off = _Style(False)
1998
+ assert _style_clock_percent(off, _ClockCell(used_percent=4.0), width=8) == " 4u/96l"
1999
+
2000
+
1919
2001
  @pytest.mark.parametrize(
1920
2002
  ("provider", "display"),
1921
2003
  [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes