aiuse 3.3.1__tar.gz → 3.3.2__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.3.1/src/aiuse.egg-info → aiuse-3.3.2}/PKG-INFO +1 -1
- {aiuse-3.3.1 → aiuse-3.3.2}/pyproject.toml +1 -1
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/__init__.py +1 -1
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/tty.py +27 -2
- {aiuse-3.3.1 → aiuse-3.3.2/src/aiuse.egg-info}/PKG-INFO +1 -1
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_tty.py +36 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/LICENSE +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/README.md +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/setup.cfg +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/__main__.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/agent_notes.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/ai_stub.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/__init__.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/history.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/local_runtimes.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/pace.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/selfdescribe.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/suggest.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/analysis/use_or_lose.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/attribute.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/chat_format.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/cli.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/__init__.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/bailian.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/base.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/caut.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/clinepass.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/codexbar.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/cswap.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/grok.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/hermes.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/muse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/opencode_go.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/opencode_zen.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/openrouter.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/openusage.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/openusage_sh.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/qwencloud.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/runner.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/throttle.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/collectors/tokscale.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/config.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/credentials.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/ledger.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/macos_trust.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/models.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/report.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/sampler.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/secretspec.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/serve.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/tui/__init__.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/tui/app.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/tui/builders.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse/watch.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse.egg-info/SOURCES.txt +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse.egg-info/dependency_links.txt +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse.egg-info/entry_points.txt +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse.egg-info/requires.txt +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/src/aiuse.egg-info/top_level.txt +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_attribution.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_bailian.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_base_run_json.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_caut_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_chat_format.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_claude_sublimits.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_cli.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_clinepass.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_codexbar_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_config.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_credentials.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_cswap_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_grok.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_hermes_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_history.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_install_deps.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_local_runtimes.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_macos_trust.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_models_cli_binary.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_models_pace.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_muse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_opencode_go.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_opencode_zen.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_openrouter.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_openusage_cli_resolution.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_openusage_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_pace.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_query_throttle.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_qwencloud.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_release_script.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_report.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_runner_consolidation.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_selfdescribe.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_serve.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_shared_quota_semantics.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_snapshots_persistence.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_suggest.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_tokscale_parse.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_tui.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_use_or_lose.py +0 -0
- {aiuse-3.3.1 → aiuse-3.3.2}/tests/test_watch.py +0 -0
|
@@ -15,6 +15,7 @@ three are normally the same device), so ``aiuse </dev/null`` is protected too.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import os
|
|
18
19
|
import sys
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
@@ -48,8 +49,27 @@ def save_stdin_tty() -> Any | None:
|
|
|
48
49
|
return None
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _owns_terminal(fd: int) -> bool:
|
|
53
|
+
"""True when this process may write terminal attributes without SIGTTOU.
|
|
54
|
+
|
|
55
|
+
``tcsetattr`` from a process that is not in the terminal's foreground
|
|
56
|
+
process group raises SIGTTOU, which *stops* the process (``aiuse &``,
|
|
57
|
+
``brew test``, any job-control runner): a hang, not an error. Such a
|
|
58
|
+
process has no business resetting someone else's terminal anyway; the
|
|
59
|
+
foreground shell restores it at its next prompt.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
return os.tcgetpgrp(fd) == os.getpgrp()
|
|
63
|
+
except (OSError, AttributeError):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
51
67
|
def restore_stdin_tty(saved: Any | None) -> None:
|
|
52
|
-
"""Restore termios attrs previously returned by ``save_stdin_tty``.
|
|
68
|
+
"""Restore termios attrs previously returned by ``save_stdin_tty``.
|
|
69
|
+
|
|
70
|
+
A no-op when the attributes are unchanged (the common case) and when this
|
|
71
|
+
process is not the terminal's foreground job (see ``_owns_terminal``).
|
|
72
|
+
"""
|
|
53
73
|
if saved is None:
|
|
54
74
|
return
|
|
55
75
|
try:
|
|
@@ -58,6 +78,11 @@ def restore_stdin_tty(saved: Any | None) -> None:
|
|
|
58
78
|
return
|
|
59
79
|
try:
|
|
60
80
|
fd, attrs = saved
|
|
61
|
-
|
|
81
|
+
fd = int(fd)
|
|
82
|
+
if termios.tcgetattr(fd) == attrs:
|
|
83
|
+
return
|
|
84
|
+
if not _owns_terminal(fd):
|
|
85
|
+
return
|
|
86
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, attrs)
|
|
62
87
|
except (termios.error, OSError, ValueError, TypeError):
|
|
63
88
|
return
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
5
7
|
from aiuse import tty
|
|
6
8
|
from aiuse.collectors import runner
|
|
7
9
|
from aiuse.tty import restore_stdin_tty, save_stdin_tty
|
|
@@ -50,11 +52,45 @@ def test_restore_targets_the_saved_fd(monkeypatch):
|
|
|
50
52
|
import termios
|
|
51
53
|
|
|
52
54
|
calls: list[tuple[int, int, object]] = []
|
|
55
|
+
monkeypatch.setattr(termios, "tcgetattr", lambda fd: ["raw"])
|
|
53
56
|
monkeypatch.setattr(termios, "tcsetattr", lambda fd, when, attrs: calls.append((fd, when, attrs)))
|
|
57
|
+
monkeypatch.setattr(tty, "_owns_terminal", lambda fd: True)
|
|
54
58
|
restore_stdin_tty((2, ["attrs"]))
|
|
55
59
|
assert calls == [(2, termios.TCSADRAIN, ["attrs"])]
|
|
56
60
|
|
|
57
61
|
|
|
62
|
+
def test_restore_is_a_noop_when_nothing_changed(monkeypatch):
|
|
63
|
+
"""No tcsetattr at all in the common case (no SIGTTOU risk, no drain)."""
|
|
64
|
+
import termios
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(termios, "tcgetattr", lambda fd: ["attrs"])
|
|
67
|
+
monkeypatch.setattr(termios, "tcsetattr", lambda *_a: pytest.fail("tcsetattr on unchanged attrs"))
|
|
68
|
+
monkeypatch.setattr(tty, "_owns_terminal", lambda fd: pytest.fail("checked pgrp on unchanged attrs"))
|
|
69
|
+
restore_stdin_tty((2, ["attrs"]))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_restore_never_writes_from_a_background_job(monkeypatch):
|
|
73
|
+
"""``aiuse --version &`` with stderr on the tty: tcsetattr would SIGTTOU-stop us.
|
|
74
|
+
|
|
75
|
+
Regression: 3.3.1's ``brew test`` hung for five minutes in state T.
|
|
76
|
+
"""
|
|
77
|
+
import termios
|
|
78
|
+
|
|
79
|
+
monkeypatch.setattr(termios, "tcgetattr", lambda fd: ["raw"])
|
|
80
|
+
monkeypatch.setattr(termios, "tcsetattr", lambda *_a: pytest.fail("tcsetattr from a background process group"))
|
|
81
|
+
monkeypatch.setattr(tty.os, "tcgetpgrp", lambda fd: 4242)
|
|
82
|
+
monkeypatch.setattr(tty.os, "getpgrp", lambda: 1)
|
|
83
|
+
restore_stdin_tty((2, ["attrs"]))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_owns_terminal_matches_foreground_pgrp(monkeypatch):
|
|
87
|
+
monkeypatch.setattr(tty.os, "tcgetpgrp", lambda fd: 7)
|
|
88
|
+
monkeypatch.setattr(tty.os, "getpgrp", lambda: 7)
|
|
89
|
+
assert tty._owns_terminal(2)
|
|
90
|
+
monkeypatch.setattr(tty.os, "tcgetpgrp", lambda fd: (_ for _ in ()).throw(OSError("not a tty")))
|
|
91
|
+
assert not tty._owns_terminal(2)
|
|
92
|
+
|
|
93
|
+
|
|
58
94
|
def test_run_collectors_restores_the_terminal_before_returning(monkeypatch):
|
|
59
95
|
"""A child that leaves the terminal raw must not corrupt the report printed next."""
|
|
60
96
|
events: list[str] = []
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|