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.
- {aiuse-3.2.4/src/aiuse.egg-info → aiuse-3.2.5}/PKG-INFO +1 -1
- {aiuse-3.2.4 → aiuse-3.2.5}/pyproject.toml +1 -1
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/__init__.py +1 -1
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/report.py +20 -22
- {aiuse-3.2.4 → aiuse-3.2.5/src/aiuse.egg-info}/PKG-INFO +1 -1
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_report.py +82 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/LICENSE +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/README.md +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/setup.cfg +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/__main__.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/agent_notes.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/ai_stub.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/__init__.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/history.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/local_runtimes.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/pace.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/selfdescribe.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/suggest.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/analysis/use_or_lose.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/attribute.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/chat_format.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/cli.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/__init__.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/bailian.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/base.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/caut.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/clinepass.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/codexbar.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/cswap.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/grok.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/hermes.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/muse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/opencode_go.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/opencode_zen.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openrouter.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openusage.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/openusage_sh.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/qwencloud.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/runner.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/collectors/tokscale.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/config.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/credentials.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/ledger.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/macos_trust.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/models.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/sampler.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/secretspec.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/serve.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tty.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/__init__.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/app.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/tui/builders.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse/watch.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/SOURCES.txt +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/dependency_links.txt +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/entry_points.txt +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/requires.txt +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/src/aiuse.egg-info/top_level.txt +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_attribution.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_bailian.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_base_run_json.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_caut_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_chat_format.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_cli.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_clinepass.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_codexbar_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_config.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_credentials.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_cswap_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_grok.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_hermes_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_history.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_install_deps.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_local_runtimes.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_macos_trust.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_models_cli_binary.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_models_pace.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_muse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_opencode_go.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_opencode_zen.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_openrouter.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_openusage_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_pace.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_qwencloud.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_release_script.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_runner_consolidation.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_selfdescribe.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_serve.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_shared_quota_semantics.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_snapshots_persistence.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_suggest.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tokscale_parse.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tty.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_tui.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_use_or_lose.py +0 -0
- {aiuse-3.2.4 → aiuse-3.2.5}/tests/test_watch.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
|
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
|