travelmaxx 0.1.0__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.
- travelmaxx-0.1.0/PKG-INFO +129 -0
- travelmaxx-0.1.0/README.md +109 -0
- travelmaxx-0.1.0/pyproject.toml +41 -0
- travelmaxx-0.1.0/setup.cfg +4 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/PKG-INFO +129 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/SOURCES.txt +20 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/dependency_links.txt +1 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/entry_points.txt +2 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/requires.txt +5 -0
- travelmaxx-0.1.0/src/travelmaxx.egg-info/top_level.txt +1 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/__init__.py +3 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/api.py +90 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/auth.py +235 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/commands/__init__.py +0 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/commands/alerts.py +56 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/commands/auth_cmds.py +38 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/commands/discover.py +19 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/commands/search.py +115 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/config.py +55 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/main.py +133 -0
- travelmaxx-0.1.0/src/travelmaxx_cli/render.py +100 -0
- travelmaxx-0.1.0/tests/test_cli.py +190 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: travelmaxx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Search award flights across 14 mileage programs from your terminal
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://travelmaxx.app
|
|
7
|
+
Project-URL: Documentation, https://travelmaxx.app/cli/docs
|
|
8
|
+
Keywords: awards,flights,miles,points,travel
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
19
|
+
Requires-Dist: respx>=0.22; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# travelmaxx
|
|
22
|
+
|
|
23
|
+
Search award flights across 14 mileage programs from your terminal, powered by
|
|
24
|
+
[TravelMaxx](https://travelmaxx.app).
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
curl -fsSL https://travelmaxx.app/cli | sh
|
|
28
|
+
travelmaxx login
|
|
29
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Any of:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
curl -fsSL https://travelmaxx.app/cli | sh # auto-detects uv/pipx/pip
|
|
38
|
+
uv tool install travelmaxx
|
|
39
|
+
pipx install travelmaxx
|
|
40
|
+
brew install travelmaxx/travelmaxx/travelmaxx # once the tap is live
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.10+ (macOS or Linux), an active TravelMaxx subscription, and
|
|
44
|
+
Roame credentials linked at <https://travelmaxx.app/dashboard/settings>.
|
|
45
|
+
|
|
46
|
+
## Sign in
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
travelmaxx login # opens your browser; click Approve
|
|
50
|
+
travelmaxx login --no-browser # headless/SSH: 6-digit code at travelmaxx.app/activate
|
|
51
|
+
travelmaxx whoami
|
|
52
|
+
travelmaxx logout
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Tokens are stored in `~/.config/travelmaxx/credentials.json` (chmod 600) and
|
|
56
|
+
refresh automatically for 30 days. Your Roame credentials never touch this
|
|
57
|
+
machine — searches run through the TravelMaxx server.
|
|
58
|
+
|
|
59
|
+
## Search
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# Single date (always pass --programs when you know them: 40-50x faster)
|
|
63
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
64
|
+
|
|
65
|
+
# Business class, 2 passengers, flexible ±2 days
|
|
66
|
+
travelmaxx search SFO CDG --date 2026-09-12 --class BUSINESS \
|
|
67
|
+
--programs ALASKA,UNITED --pax 2 --days-around 2
|
|
68
|
+
|
|
69
|
+
# A whole month in parallel, with a live progress bar
|
|
70
|
+
travelmaxx search SFO NRT --date-range november --programs ALASKA
|
|
71
|
+
|
|
72
|
+
# Explicit range / year / "next year"
|
|
73
|
+
travelmaxx search SFO HEL --date-range 2026-09-12:2026-10-18 --programs ALASKA
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Options: `--sort points|duration`, `--limit N`, `--max-wait SECONDS` (5–120;
|
|
77
|
+
results are marked `partial` if the search returns early), `--min-results N`,
|
|
78
|
+
`--max-workers N` (range searches), `--json`, `--output-file FILE`.
|
|
79
|
+
|
|
80
|
+
Programs: `ALASKA, UNITED, DELTA, JETBLUE, LIFEMILES` (fast) ·
|
|
81
|
+
`AMERICAN, QANTAS, EMIRATES, VIRGIN_ATLANTIC, VIRGIN_AUSTRALIA, CLUB_PREMIER,
|
|
82
|
+
ETIHAD, FLYING_BLUE, GOL` (slower).
|
|
83
|
+
|
|
84
|
+
## Alerts & discovery
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
travelmaxx alerts create SFO NRT --class BUSINESS --max-points 75000
|
|
88
|
+
travelmaxx alerts list
|
|
89
|
+
travelmaxx alerts delete <uuid>
|
|
90
|
+
travelmaxx discover --limit 10
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## JSON output
|
|
94
|
+
|
|
95
|
+
`--json` prints the full API response. Single-date searches return
|
|
96
|
+
`{"fares": [...], "percent_completed": 100, "partial": false}`; job-based
|
|
97
|
+
searches return `{"status", "percent", "fares_count", "fares", ...}`.
|
|
98
|
+
Each fare:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"departure_date": "2026-10-05",
|
|
103
|
+
"origin": "SFO", "destination": "NRT",
|
|
104
|
+
"mileage_program": "ALASKA",
|
|
105
|
+
"award_points": 75000, "surcharge_usd": 18.1,
|
|
106
|
+
"num_stops": 0, "duration_minutes": 630,
|
|
107
|
+
"cabin_classes": ["BUSINESS"], "operating_airlines": ["JL"],
|
|
108
|
+
"equipment_types": ["77W"], "available_seats": 2, "roame_score": 1684
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
| Env var | Purpose |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `TRAVELMAXX_API_URL` | Point at a different server (also `--server URL`) |
|
|
117
|
+
| `XDG_CONFIG_HOME` | Relocate the config dir (default `~/.config/travelmaxx`) |
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
cd cli
|
|
123
|
+
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
124
|
+
.venv/bin/pytest
|
|
125
|
+
.venv/bin/travelmaxx --server http://localhost:8000 login
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Design doc: `../CLI.md`. Releases: tag `cli-vX.Y.Z` (see
|
|
129
|
+
`.github/workflows/release-cli.yml`).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# travelmaxx
|
|
2
|
+
|
|
3
|
+
Search award flights across 14 mileage programs from your terminal, powered by
|
|
4
|
+
[TravelMaxx](https://travelmaxx.app).
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
curl -fsSL https://travelmaxx.app/cli | sh
|
|
8
|
+
travelmaxx login
|
|
9
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Any of:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
curl -fsSL https://travelmaxx.app/cli | sh # auto-detects uv/pipx/pip
|
|
18
|
+
uv tool install travelmaxx
|
|
19
|
+
pipx install travelmaxx
|
|
20
|
+
brew install travelmaxx/travelmaxx/travelmaxx # once the tap is live
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires Python 3.10+ (macOS or Linux), an active TravelMaxx subscription, and
|
|
24
|
+
Roame credentials linked at <https://travelmaxx.app/dashboard/settings>.
|
|
25
|
+
|
|
26
|
+
## Sign in
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
travelmaxx login # opens your browser; click Approve
|
|
30
|
+
travelmaxx login --no-browser # headless/SSH: 6-digit code at travelmaxx.app/activate
|
|
31
|
+
travelmaxx whoami
|
|
32
|
+
travelmaxx logout
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Tokens are stored in `~/.config/travelmaxx/credentials.json` (chmod 600) and
|
|
36
|
+
refresh automatically for 30 days. Your Roame credentials never touch this
|
|
37
|
+
machine — searches run through the TravelMaxx server.
|
|
38
|
+
|
|
39
|
+
## Search
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# Single date (always pass --programs when you know them: 40-50x faster)
|
|
43
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
44
|
+
|
|
45
|
+
# Business class, 2 passengers, flexible ±2 days
|
|
46
|
+
travelmaxx search SFO CDG --date 2026-09-12 --class BUSINESS \
|
|
47
|
+
--programs ALASKA,UNITED --pax 2 --days-around 2
|
|
48
|
+
|
|
49
|
+
# A whole month in parallel, with a live progress bar
|
|
50
|
+
travelmaxx search SFO NRT --date-range november --programs ALASKA
|
|
51
|
+
|
|
52
|
+
# Explicit range / year / "next year"
|
|
53
|
+
travelmaxx search SFO HEL --date-range 2026-09-12:2026-10-18 --programs ALASKA
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Options: `--sort points|duration`, `--limit N`, `--max-wait SECONDS` (5–120;
|
|
57
|
+
results are marked `partial` if the search returns early), `--min-results N`,
|
|
58
|
+
`--max-workers N` (range searches), `--json`, `--output-file FILE`.
|
|
59
|
+
|
|
60
|
+
Programs: `ALASKA, UNITED, DELTA, JETBLUE, LIFEMILES` (fast) ·
|
|
61
|
+
`AMERICAN, QANTAS, EMIRATES, VIRGIN_ATLANTIC, VIRGIN_AUSTRALIA, CLUB_PREMIER,
|
|
62
|
+
ETIHAD, FLYING_BLUE, GOL` (slower).
|
|
63
|
+
|
|
64
|
+
## Alerts & discovery
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
travelmaxx alerts create SFO NRT --class BUSINESS --max-points 75000
|
|
68
|
+
travelmaxx alerts list
|
|
69
|
+
travelmaxx alerts delete <uuid>
|
|
70
|
+
travelmaxx discover --limit 10
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## JSON output
|
|
74
|
+
|
|
75
|
+
`--json` prints the full API response. Single-date searches return
|
|
76
|
+
`{"fares": [...], "percent_completed": 100, "partial": false}`; job-based
|
|
77
|
+
searches return `{"status", "percent", "fares_count", "fares", ...}`.
|
|
78
|
+
Each fare:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"departure_date": "2026-10-05",
|
|
83
|
+
"origin": "SFO", "destination": "NRT",
|
|
84
|
+
"mileage_program": "ALASKA",
|
|
85
|
+
"award_points": 75000, "surcharge_usd": 18.1,
|
|
86
|
+
"num_stops": 0, "duration_minutes": 630,
|
|
87
|
+
"cabin_classes": ["BUSINESS"], "operating_airlines": ["JL"],
|
|
88
|
+
"equipment_types": ["77W"], "available_seats": 2, "roame_score": 1684
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
| Env var | Purpose |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `TRAVELMAXX_API_URL` | Point at a different server (also `--server URL`) |
|
|
97
|
+
| `XDG_CONFIG_HOME` | Relocate the config dir (default `~/.config/travelmaxx`) |
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
cd cli
|
|
103
|
+
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
104
|
+
.venv/bin/pytest
|
|
105
|
+
.venv/bin/travelmaxx --server http://localhost:8000 login
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Design doc: `../CLI.md`. Releases: tag `cli-vX.Y.Z` (see
|
|
109
|
+
`.github/workflows/release-cli.yml`).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "travelmaxx"
|
|
3
|
+
description = "Search award flights across 14 mileage programs from your terminal"
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"httpx>=0.27",
|
|
10
|
+
]
|
|
11
|
+
keywords = ["awards", "flights", "miles", "points", "travel"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Environment :: Console",
|
|
14
|
+
"Intended Audience :: End Users/Desktop",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Topic :: Utilities",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://travelmaxx.app"
|
|
22
|
+
Documentation = "https://travelmaxx.app/cli/docs"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
travelmaxx = "travelmaxx_cli.main:main"
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=8.0",
|
|
30
|
+
"respx>=0.22",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["setuptools>=75"]
|
|
35
|
+
build-backend = "setuptools.build_meta"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.dynamic]
|
|
38
|
+
version = { attr = "travelmaxx_cli.__version__" }
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: travelmaxx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Search award flights across 14 mileage programs from your terminal
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://travelmaxx.app
|
|
7
|
+
Project-URL: Documentation, https://travelmaxx.app/cli/docs
|
|
8
|
+
Keywords: awards,flights,miles,points,travel
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
19
|
+
Requires-Dist: respx>=0.22; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# travelmaxx
|
|
22
|
+
|
|
23
|
+
Search award flights across 14 mileage programs from your terminal, powered by
|
|
24
|
+
[TravelMaxx](https://travelmaxx.app).
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
curl -fsSL https://travelmaxx.app/cli | sh
|
|
28
|
+
travelmaxx login
|
|
29
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Any of:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
curl -fsSL https://travelmaxx.app/cli | sh # auto-detects uv/pipx/pip
|
|
38
|
+
uv tool install travelmaxx
|
|
39
|
+
pipx install travelmaxx
|
|
40
|
+
brew install travelmaxx/travelmaxx/travelmaxx # once the tap is live
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.10+ (macOS or Linux), an active TravelMaxx subscription, and
|
|
44
|
+
Roame credentials linked at <https://travelmaxx.app/dashboard/settings>.
|
|
45
|
+
|
|
46
|
+
## Sign in
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
travelmaxx login # opens your browser; click Approve
|
|
50
|
+
travelmaxx login --no-browser # headless/SSH: 6-digit code at travelmaxx.app/activate
|
|
51
|
+
travelmaxx whoami
|
|
52
|
+
travelmaxx logout
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Tokens are stored in `~/.config/travelmaxx/credentials.json` (chmod 600) and
|
|
56
|
+
refresh automatically for 30 days. Your Roame credentials never touch this
|
|
57
|
+
machine — searches run through the TravelMaxx server.
|
|
58
|
+
|
|
59
|
+
## Search
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# Single date (always pass --programs when you know them: 40-50x faster)
|
|
63
|
+
travelmaxx search SFO NRT --date 2026-10-05 --programs ALASKA
|
|
64
|
+
|
|
65
|
+
# Business class, 2 passengers, flexible ±2 days
|
|
66
|
+
travelmaxx search SFO CDG --date 2026-09-12 --class BUSINESS \
|
|
67
|
+
--programs ALASKA,UNITED --pax 2 --days-around 2
|
|
68
|
+
|
|
69
|
+
# A whole month in parallel, with a live progress bar
|
|
70
|
+
travelmaxx search SFO NRT --date-range november --programs ALASKA
|
|
71
|
+
|
|
72
|
+
# Explicit range / year / "next year"
|
|
73
|
+
travelmaxx search SFO HEL --date-range 2026-09-12:2026-10-18 --programs ALASKA
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Options: `--sort points|duration`, `--limit N`, `--max-wait SECONDS` (5–120;
|
|
77
|
+
results are marked `partial` if the search returns early), `--min-results N`,
|
|
78
|
+
`--max-workers N` (range searches), `--json`, `--output-file FILE`.
|
|
79
|
+
|
|
80
|
+
Programs: `ALASKA, UNITED, DELTA, JETBLUE, LIFEMILES` (fast) ·
|
|
81
|
+
`AMERICAN, QANTAS, EMIRATES, VIRGIN_ATLANTIC, VIRGIN_AUSTRALIA, CLUB_PREMIER,
|
|
82
|
+
ETIHAD, FLYING_BLUE, GOL` (slower).
|
|
83
|
+
|
|
84
|
+
## Alerts & discovery
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
travelmaxx alerts create SFO NRT --class BUSINESS --max-points 75000
|
|
88
|
+
travelmaxx alerts list
|
|
89
|
+
travelmaxx alerts delete <uuid>
|
|
90
|
+
travelmaxx discover --limit 10
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## JSON output
|
|
94
|
+
|
|
95
|
+
`--json` prints the full API response. Single-date searches return
|
|
96
|
+
`{"fares": [...], "percent_completed": 100, "partial": false}`; job-based
|
|
97
|
+
searches return `{"status", "percent", "fares_count", "fares", ...}`.
|
|
98
|
+
Each fare:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"departure_date": "2026-10-05",
|
|
103
|
+
"origin": "SFO", "destination": "NRT",
|
|
104
|
+
"mileage_program": "ALASKA",
|
|
105
|
+
"award_points": 75000, "surcharge_usd": 18.1,
|
|
106
|
+
"num_stops": 0, "duration_minutes": 630,
|
|
107
|
+
"cabin_classes": ["BUSINESS"], "operating_airlines": ["JL"],
|
|
108
|
+
"equipment_types": ["77W"], "available_seats": 2, "roame_score": 1684
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
| Env var | Purpose |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `TRAVELMAXX_API_URL` | Point at a different server (also `--server URL`) |
|
|
117
|
+
| `XDG_CONFIG_HOME` | Relocate the config dir (default `~/.config/travelmaxx`) |
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
cd cli
|
|
123
|
+
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
124
|
+
.venv/bin/pytest
|
|
125
|
+
.venv/bin/travelmaxx --server http://localhost:8000 login
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Design doc: `../CLI.md`. Releases: tag `cli-vX.Y.Z` (see
|
|
129
|
+
`.github/workflows/release-cli.yml`).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/travelmaxx.egg-info/PKG-INFO
|
|
4
|
+
src/travelmaxx.egg-info/SOURCES.txt
|
|
5
|
+
src/travelmaxx.egg-info/dependency_links.txt
|
|
6
|
+
src/travelmaxx.egg-info/entry_points.txt
|
|
7
|
+
src/travelmaxx.egg-info/requires.txt
|
|
8
|
+
src/travelmaxx.egg-info/top_level.txt
|
|
9
|
+
src/travelmaxx_cli/__init__.py
|
|
10
|
+
src/travelmaxx_cli/api.py
|
|
11
|
+
src/travelmaxx_cli/auth.py
|
|
12
|
+
src/travelmaxx_cli/config.py
|
|
13
|
+
src/travelmaxx_cli/main.py
|
|
14
|
+
src/travelmaxx_cli/render.py
|
|
15
|
+
src/travelmaxx_cli/commands/__init__.py
|
|
16
|
+
src/travelmaxx_cli/commands/alerts.py
|
|
17
|
+
src/travelmaxx_cli/commands/auth_cmds.py
|
|
18
|
+
src/travelmaxx_cli/commands/discover.py
|
|
19
|
+
src/travelmaxx_cli/commands/search.py
|
|
20
|
+
tests/test_cli.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
travelmaxx_cli
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""HTTP client for the TravelMaxx /api/v1 REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from . import auth, config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class APIError(Exception):
|
|
11
|
+
def __init__(self, status: int, code: str, message: str):
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
self.status = status
|
|
14
|
+
self.code = code
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _friendly(status: int, code: str, message: str) -> APIError:
|
|
18
|
+
if status == 402:
|
|
19
|
+
message = "Active subscription required — start a trial at https://travelmaxx.app/subscribe"
|
|
20
|
+
elif status == 409:
|
|
21
|
+
message = ("No Roame account linked — connect it at "
|
|
22
|
+
"https://travelmaxx.app/dashboard/settings")
|
|
23
|
+
return APIError(status, code, message)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class APIClient:
|
|
27
|
+
def __init__(self, server: str | None = None):
|
|
28
|
+
self._creds = auth.get_valid_credentials(server)
|
|
29
|
+
self._server = self._creds["server"]
|
|
30
|
+
self._http = httpx.Client(base_url=self._server, timeout=180.0)
|
|
31
|
+
|
|
32
|
+
def close(self):
|
|
33
|
+
self._http.close()
|
|
34
|
+
|
|
35
|
+
def __enter__(self):
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def __exit__(self, *exc):
|
|
39
|
+
self.close()
|
|
40
|
+
|
|
41
|
+
def _request(self, method: str, path: str, *, retry_auth: bool = True, **kwargs):
|
|
42
|
+
headers = {"Authorization": f"Bearer {self._creds['access_token']}"}
|
|
43
|
+
resp = self._http.request(method, path, headers=headers, **kwargs)
|
|
44
|
+
if resp.status_code == 401 and retry_auth:
|
|
45
|
+
self._creds = auth.refresh_credentials(self._creds)
|
|
46
|
+
return self._request(method, path, retry_auth=False, **kwargs)
|
|
47
|
+
if resp.status_code >= 400:
|
|
48
|
+
try:
|
|
49
|
+
body = resp.json()
|
|
50
|
+
code, message = body.get("error", ""), body.get("message", "")
|
|
51
|
+
except Exception:
|
|
52
|
+
code, message = f"http_{resp.status_code}", resp.text[:200]
|
|
53
|
+
raise _friendly(resp.status_code, code, message or code)
|
|
54
|
+
return resp.json()
|
|
55
|
+
|
|
56
|
+
# ── Endpoints ───────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def me(self) -> dict:
|
|
59
|
+
return self._request("GET", "/api/v1/me")
|
|
60
|
+
|
|
61
|
+
def search(self, **body) -> dict:
|
|
62
|
+
return self._request("POST", "/api/v1/search",
|
|
63
|
+
json={k: v for k, v in body.items() if v is not None})
|
|
64
|
+
|
|
65
|
+
def create_search_job(self, **body) -> dict:
|
|
66
|
+
return self._request("POST", "/api/v1/search/jobs",
|
|
67
|
+
json={k: v for k, v in body.items() if v is not None})
|
|
68
|
+
|
|
69
|
+
def get_search_job(self, job_id: str, include_fares: bool = True,
|
|
70
|
+
sort_by: str = "points", limit: int = 500) -> dict:
|
|
71
|
+
return self._request(
|
|
72
|
+
"GET", f"/api/v1/search/jobs/{job_id}",
|
|
73
|
+
params={"include_fares": str(include_fares).lower(),
|
|
74
|
+
"sort_by": sort_by, "limit": limit})
|
|
75
|
+
|
|
76
|
+
def cancel_search_job(self, job_id: str) -> dict:
|
|
77
|
+
return self._request("DELETE", f"/api/v1/search/jobs/{job_id}")
|
|
78
|
+
|
|
79
|
+
def list_alerts(self) -> list[dict]:
|
|
80
|
+
return self._request("GET", "/api/v1/alerts")
|
|
81
|
+
|
|
82
|
+
def create_alert(self, **body) -> dict:
|
|
83
|
+
return self._request("POST", "/api/v1/alerts",
|
|
84
|
+
json={k: v for k, v in body.items() if v is not None})
|
|
85
|
+
|
|
86
|
+
def delete_alert(self, uuid: str) -> dict:
|
|
87
|
+
return self._request("DELETE", f"/api/v1/alerts/{uuid}")
|
|
88
|
+
|
|
89
|
+
def discover(self, limit: int = 20) -> list[dict]:
|
|
90
|
+
return self._request("GET", "/api/v1/discover", params={"limit": limit})
|