avios-cli 0.6.0__tar.gz → 0.6.1__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.
- {avios_cli-0.6.0 → avios_cli-0.6.1}/CHANGELOG.md +8 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/PKG-INFO +1 -1
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/__init__.py +1 -1
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/auth.py +20 -4
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_auth.py +23 -4
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.editorconfig +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/dependabot.yml +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/workflows/ci.yml +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/workflows/publish.yml +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.gitignore +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.pre-commit-config.yaml +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/.python-version +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/CONTRIBUTING.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/LICENSE +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/README.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/SECURITY.md +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/docs/dashboard.svg +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/docs/reward-flights.svg +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/pyproject.toml +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/scripts/screenshot.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/accounts.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/aggregate.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/cli.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/client.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/config.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/endpoints.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/finnair.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/mcp_entry.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/mcp_server.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/models.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/programmes.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/rewards.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/session.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/__init__.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/app.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/art.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/rewards.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/styles.tcss +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/widgets.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/conftest.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/balance.json +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/reward_search.rsc +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/transactions.json +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_accounts.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_aggregate.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_cli.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_client.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_finnair.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_mcp_server.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_models.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_rewards.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_session.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_tui.py +0 -0
- {avios_cli-0.6.0 → avios_cli-0.6.1}/uv.lock +0 -0
|
@@ -6,6 +6,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.6.1] - 2026-11-11
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `avios login ba` no longer reports "reward-flight login did not succeed" when BA's
|
|
13
|
+
reward app runs on the main `ba_session_id` session instead of issuing a separate
|
|
14
|
+
`__session`/`appSession*` cookie. The login now completes once the reward page is
|
|
15
|
+
loaded with a valid session.
|
|
16
|
+
|
|
9
17
|
## [0.6.0] - 2026-08-11
|
|
10
18
|
|
|
11
19
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: avios-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: A CLI, TUI and MCP server for Avios balances, transactions, and BA reward-flight availability.
|
|
5
5
|
Project-URL: Homepage, https://github.com/alexechoi/avios-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/alexechoi/avios-cli
|
|
@@ -33,6 +33,7 @@ from avios.session import cookie_header_from
|
|
|
33
33
|
LOGIN_TIMEOUT_MS = 300_000 # 5 minutes to complete password + captcha + MFA
|
|
34
34
|
LOGIN_POLL_MS = 2_000 # how often to check whether the session is authenticated
|
|
35
35
|
SUPPORTED_BROWSERS = ("chrome", "firefox", "edge", "brave", "safari", "chromium")
|
|
36
|
+
BA_ACCOUNT_SESSION_COOKIE = "ba_session_id"
|
|
36
37
|
BA_REWARD_SEARCH_PATH = "/en-GB/spend-avios/search-reward-flights"
|
|
37
38
|
|
|
38
39
|
# Chromium-family user-data roots per browser (relative to home), used to discover
|
|
@@ -140,14 +141,29 @@ def login_via_browser(
|
|
|
140
141
|
return _only_avios(cookies)
|
|
141
142
|
|
|
142
143
|
|
|
143
|
-
def
|
|
144
|
-
|
|
145
|
-
names = {
|
|
144
|
+
def _avios_cookie_names(ctx: Any) -> set[str]:
|
|
145
|
+
return {
|
|
146
146
|
str(cookie.get("name", ""))
|
|
147
147
|
for cookie in ctx.cookies()
|
|
148
148
|
if "avios.com" in str(cookie.get("domain", ""))
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _has_ba_reward_session(ctx: Any) -> bool:
|
|
153
|
+
"""Whether a session usable by BA's reward-search application is present.
|
|
154
|
+
|
|
155
|
+
BA historically issued a separate ``__session``/``appSession*`` cookie for the
|
|
156
|
+
reward app. It now also serves the reward app from the main ``ba_session_id``
|
|
157
|
+
session without setting those cookies, so that cookie counts too. The caller
|
|
158
|
+
still requires the browser to be sitting on the reward page, so a redirect to a
|
|
159
|
+
separate login keeps the wait going.
|
|
160
|
+
"""
|
|
161
|
+
names = _avios_cookie_names(ctx)
|
|
162
|
+
return (
|
|
163
|
+
"__session" in names
|
|
164
|
+
or BA_ACCOUNT_SESSION_COOKIE in names
|
|
165
|
+
or any(name.startswith("appSession") for name in names)
|
|
166
|
+
)
|
|
151
167
|
|
|
152
168
|
|
|
153
169
|
def _wait_for_ba_reward_auth(ctx: Any, page: Any, base_url: str, timeout_ms: int) -> bool:
|
|
@@ -124,13 +124,14 @@ class _FakeRequest:
|
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
class _FakePage:
|
|
127
|
-
def __init__(self) -> None:
|
|
127
|
+
def __init__(self, redirect_to: str | None = None) -> None:
|
|
128
128
|
self.visited: list[str] = []
|
|
129
129
|
self.url = ""
|
|
130
|
+
self._redirect_to = redirect_to
|
|
130
131
|
|
|
131
132
|
def goto(self, url: str, **kwargs: object) -> None:
|
|
132
133
|
self.visited.append(url)
|
|
133
|
-
self.url = url
|
|
134
|
+
self.url = self._redirect_to or url
|
|
134
135
|
|
|
135
136
|
def wait_for_timeout(self, ms: int) -> None:
|
|
136
137
|
pass
|
|
@@ -140,11 +141,16 @@ class _FakePage:
|
|
|
140
141
|
|
|
141
142
|
|
|
142
143
|
class _FakeContext:
|
|
143
|
-
def __init__(
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
ok_after: int,
|
|
147
|
+
cookies: list[dict[str, str]],
|
|
148
|
+
redirect_to: str | None = None,
|
|
149
|
+
) -> None:
|
|
144
150
|
self.request = _FakeRequest(ok_after)
|
|
145
151
|
self._cookies = cookies
|
|
146
152
|
self.closed = False
|
|
147
|
-
self.page = _FakePage()
|
|
153
|
+
self.page = _FakePage(redirect_to)
|
|
148
154
|
|
|
149
155
|
def new_page(self) -> _FakePage:
|
|
150
156
|
return self.page
|
|
@@ -208,10 +214,23 @@ def test_login_captures_only_after_authenticated(settings: Settings) -> None:
|
|
|
208
214
|
assert ctx.page.visited[-1].endswith("/en-GB/spend-avios/search-reward-flights")
|
|
209
215
|
|
|
210
216
|
|
|
217
|
+
def test_ba_login_accepts_account_session_on_reward_page(settings: Settings) -> None:
|
|
218
|
+
cookies = [{"name": "ba_session_id", "value": "x", "domain": "www.avios.com"}]
|
|
219
|
+
ctx = _FakeContext(ok_after=1, cookies=cookies)
|
|
220
|
+
|
|
221
|
+
captured = login_via_browser(
|
|
222
|
+
BA, settings=settings, timeout_ms=4_000, playwright_factory=_FakeFactory(ctx)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
assert captured == cookies
|
|
226
|
+
assert ctx.page.visited[-1].endswith("/en-GB/spend-avios/search-reward-flights")
|
|
227
|
+
|
|
228
|
+
|
|
211
229
|
def test_ba_login_waits_for_separate_reward_session(settings: Settings) -> None:
|
|
212
230
|
ctx = _FakeContext(
|
|
213
231
|
ok_after=1,
|
|
214
232
|
cookies=[{"name": "ba_session_id", "value": "x", "domain": "www.avios.com"}],
|
|
233
|
+
redirect_to="https://www.avios.com/reward-login",
|
|
215
234
|
)
|
|
216
235
|
|
|
217
236
|
with pytest.raises(LoginError, match="reward-flight login"):
|
|
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
|