cli-web-airbnb 0.1.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.
Files changed (30) hide show
  1. cli_web_airbnb-0.1.1/PKG-INFO +12 -0
  2. cli_web_airbnb-0.1.1/cli_web/airbnb/README.md +236 -0
  3. cli_web_airbnb-0.1.1/cli_web/airbnb/__init__.py +3 -0
  4. cli_web_airbnb-0.1.1/cli_web/airbnb/__main__.py +6 -0
  5. cli_web_airbnb-0.1.1/cli_web/airbnb/airbnb_cli.py +169 -0
  6. cli_web_airbnb-0.1.1/cli_web/airbnb/commands/autocomplete.py +100 -0
  7. cli_web_airbnb-0.1.1/cli_web/airbnb/commands/listings.py +294 -0
  8. cli_web_airbnb-0.1.1/cli_web/airbnb/commands/search.py +205 -0
  9. cli_web_airbnb-0.1.1/cli_web/airbnb/core/__init__.py +1 -0
  10. cli_web_airbnb-0.1.1/cli_web/airbnb/core/client.py +515 -0
  11. cli_web_airbnb-0.1.1/cli_web/airbnb/core/exceptions.py +84 -0
  12. cli_web_airbnb-0.1.1/cli_web/airbnb/core/models.py +137 -0
  13. cli_web_airbnb-0.1.1/cli_web/airbnb/skills/SKILL.md +132 -0
  14. cli_web_airbnb-0.1.1/cli_web/airbnb/tests/TEST.md +295 -0
  15. cli_web_airbnb-0.1.1/cli_web/airbnb/tests/test_core.py +647 -0
  16. cli_web_airbnb-0.1.1/cli_web/airbnb/tests/test_e2e.py +503 -0
  17. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/config.py +24 -0
  18. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/doctor.py +188 -0
  19. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/helpers.py +107 -0
  20. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/mcp_server.py +290 -0
  21. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/output.py +12 -0
  22. cli_web_airbnb-0.1.1/cli_web/airbnb/utils/repl_skin.py +486 -0
  23. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/PKG-INFO +12 -0
  24. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/SOURCES.txt +28 -0
  25. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/dependency_links.txt +1 -0
  26. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/entry_points.txt +2 -0
  27. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/requires.txt +4 -0
  28. cli_web_airbnb-0.1.1/cli_web_airbnb.egg-info/top_level.txt +1 -0
  29. cli_web_airbnb-0.1.1/setup.cfg +4 -0
  30. cli_web_airbnb-0.1.1/setup.py +23 -0
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: cli-web-airbnb
3
+ Version: 0.1.1
4
+ Summary: CLI for Airbnb — search stays, listings, and locations
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: click>=8.0
7
+ Requires-Dist: curl_cffi>=0.5.10
8
+ Requires-Dist: rich>=13.0
9
+ Requires-Dist: prompt_toolkit>=3.0
10
+ Dynamic: requires-dist
11
+ Dynamic: requires-python
12
+ Dynamic: summary
@@ -0,0 +1,236 @@
1
+ # cli-web-airbnb
2
+
3
+ Search Airbnb stays, get listing details, and look up location suggestions — all from the terminal with structured JSON output.
4
+
5
+ > Generated by [CLI-Anything-Web](../../../../cli-anything-web-plugin/) from [airbnb.com](https://www.airbnb.com)
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install cli-web-airbnb
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Search Stays
16
+
17
+ ```bash
18
+ # Search for stays in a location
19
+ cli-web-airbnb search stays "London, UK" --json
20
+
21
+ # Search with dates and guests
22
+ cli-web-airbnb search stays "Paris, France" --checkin 2024-06-01 --checkout 2024-06-05 --adults 2 --json
23
+
24
+ # Filter by max price
25
+ cli-web-airbnb --json search stays "Tokyo, Japan" --max-price 150
26
+
27
+ # Filter by price and room type
28
+ cli-web-airbnb search stays "New York, NY" --max-price 200 --room-type entire_home
29
+
30
+ # Paginate results using a cursor from a previous response
31
+ cli-web-airbnb search stays "Tokyo, Japan" --cursor "eyJ..."
32
+ ```
33
+
34
+ ### Listings Detail
35
+
36
+ ```bash
37
+ # Get full details for a listing
38
+ cli-web-airbnb listings get 770993223449115417 --json
39
+
40
+ # With dates for pricing context
41
+ cli-web-airbnb listings get 770993223449115417 --checkin 2024-06-01 --checkout 2024-06-05 --json
42
+ ```
43
+
44
+ ### Listing Reviews
45
+
46
+ ```bash
47
+ # Get reviews for a listing (sorted by best quality)
48
+ cli-web-airbnb listings reviews 770993223449115417 --json
49
+
50
+ # Get most recent reviews
51
+ cli-web-airbnb listings reviews 770993223449115417 --sort RECENT --limit 10 --json
52
+ ```
53
+
54
+ ### Availability Calendar
55
+
56
+ ```bash
57
+ # Get 12-month availability calendar
58
+ cli-web-airbnb listings availability 770993223449115417 --json
59
+
60
+ # Get 3 months starting from a specific month
61
+ cli-web-airbnb listings availability 770993223449115417 --month 6 --year 2026 --count 3 --json
62
+ ```
63
+
64
+ ### Autocomplete Locations
65
+
66
+ ```bash
67
+ # Suggest locations for a partial query
68
+ cli-web-airbnb autocomplete locations "New Yor" --json
69
+
70
+ # With more results
71
+ cli-web-airbnb autocomplete locations "Lond" --num-results 10 --json
72
+ ```
73
+
74
+ ## Auth
75
+
76
+ Airbnb is a **no-auth public site** — no login is required. All three command groups (`search`, `listings`, `autocomplete`) work without any credentials.
77
+
78
+ Optional cookie-based auth (for wishlists and booking) is not yet implemented. The public endpoints cover all search and listing detail functionality.
79
+
80
+ ## JSON Output
81
+
82
+ Every command supports `--json` for structured output. The flag can be placed at the group level (before the subcommand) or at the subcommand level (after):
83
+
84
+ ```bash
85
+ # Group-level --json (before subcommand)
86
+ cli-web-airbnb --json search stays "London, UK"
87
+
88
+ # Subcommand-level --json (after subcommand)
89
+ cli-web-airbnb search stays "London, UK" --json
90
+
91
+ # Listings detail
92
+ cli-web-airbnb listings get 770993223449115417 --json
93
+
94
+ # Autocomplete
95
+ cli-web-airbnb autocomplete locations "New Yor" --json
96
+ ```
97
+
98
+ Example response for `search stays --json`:
99
+
100
+ ```json
101
+ {
102
+ "success": true,
103
+ "count": 18,
104
+ "next_cursor": "eyJzZWN0aW9uX29mZnNldCI6MCwiaXRlbXNfb2Zmc2V0IjoxOH0=",
105
+ "total_count": 300,
106
+ "location_slug": "London--UK",
107
+ "listings": [
108
+ {
109
+ "id": "770993223449115417",
110
+ "id_b64": "RGVtYW5kU3RheUxpc3Rpbmc6NzcwOTkzMjIzNDQ5MTE1NDE3",
111
+ "name": "Room with Wembley view",
112
+ "rating": "4.98 (42)",
113
+ "price": "$142",
114
+ "price_qualifier": "total",
115
+ "latitude": 51.5589,
116
+ "longitude": -0.2789,
117
+ "badges": ["Guest favourite"],
118
+ "url": "https://www.airbnb.com/rooms/770993223449115417"
119
+ }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ Example response for `listings get --json`:
125
+
126
+ ```json
127
+ {
128
+ "success": true,
129
+ "id": "770993223449115417",
130
+ "name": "Room with Wembley view",
131
+ "rating": "4.98 (42)",
132
+ "review_count": 42,
133
+ "host_name": "Sean",
134
+ "description": "Indulge in a stylish retreat...",
135
+ "amenities": ["Body soap", "Shower gel", "Wifi"],
136
+ "price": null,
137
+ "bedrooms": null,
138
+ "bathrooms": null,
139
+ "max_guests": null,
140
+ "url": "https://www.airbnb.com/rooms/770993223449115417"
141
+ }
142
+ ```
143
+
144
+ Example response for `listings reviews --json`:
145
+
146
+ ```json
147
+ {
148
+ "success": true,
149
+ "listing_id": "770993223449115417",
150
+ "count": 24,
151
+ "reviews": [
152
+ {
153
+ "id": "1651523546402376930",
154
+ "rating": 5,
155
+ "date": "March 2026",
156
+ "reviewer": "Craig",
157
+ "reviewer_location": "Falkirk, United Kingdom",
158
+ "comment": "Sean was a brilliant host...",
159
+ "host_response": null
160
+ }
161
+ ]
162
+ }
163
+ ```
164
+
165
+ Example response for `listings availability --json`:
166
+
167
+ ```json
168
+ {
169
+ "success": true,
170
+ "listing_id": "770993223449115417",
171
+ "months": [
172
+ {
173
+ "month": 4,
174
+ "year": 2026,
175
+ "days": [
176
+ {"date": "2026-04-06", "available": true, "checkin": true, "checkout": true, "min_nights": 1}
177
+ ]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ Example response for `autocomplete locations --json`:
184
+
185
+ ```json
186
+ {
187
+ "success": true,
188
+ "suggestions": [
189
+ {
190
+ "query": "London, United Kingdom",
191
+ "place_id": "ChIJdd4hrwug2EcRmSrV3Vo6llI",
192
+ "display": "London, United Kingdom"
193
+ }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ Error responses always follow the same shape:
199
+
200
+ ```json
201
+ {"error": true, "code": "SERVER_ERROR", "message": "No StaysSearch data in page HTML"}
202
+ ```
203
+
204
+ ## REPL Mode
205
+
206
+ Run without arguments to enter interactive mode:
207
+
208
+ ```bash
209
+ cli-web-airbnb
210
+ ```
211
+
212
+ Type `help` for available commands, `quit` to exit.
213
+
214
+ ## Testing
215
+
216
+ ```bash
217
+ cd airbnb/agent-harness
218
+ pip install -e .
219
+
220
+ # Unit tests (no network)
221
+ python -m pytest cli_web/airbnb/tests/test_core.py -v
222
+
223
+ # All tests including E2E (requires live network)
224
+ python -m pytest cli_web/airbnb/tests/ -v -s
225
+
226
+ # Subprocess tests (requires installed binary on PATH)
227
+ CLI_WEB_FORCE_INSTALLED=1 python -m pytest cli_web/airbnb/tests/test_e2e.py -v -s
228
+ ```
229
+
230
+ ## Protocol
231
+
232
+ - **Website:** https://www.airbnb.com
233
+ - **Protocol:** SSR HTML + niobeClientData (embedded GraphQL) + REST API `/api/v2/`
234
+ - **Auth:** None required (public no-auth site)
235
+ - **Bot protection:** Akamai/DataDome — bypassed with `curl_cffi` Chrome impersonation
236
+ - **API key:** `d306zoyjsyarp7ifhu67rjxn52tv0t20` (used for `/api/v2/` REST calls only, not HTML pages)
@@ -0,0 +1,3 @@
1
+ """cli-web-airbnb — CLI for Airbnb search and listings."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow python -m cli_web.airbnb execution."""
2
+
3
+ from .airbnb_cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -0,0 +1,169 @@
1
+ """cli-web-airbnb — Airbnb search and listings CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as _json
6
+ import shlex
7
+ import sys
8
+
9
+ # Windows UTF-8 fix — reconfigure both stdout and stderr
10
+ for _stream in (sys.stdout, sys.stderr):
11
+ if (
12
+ _stream
13
+ and getattr(_stream, "encoding", None)
14
+ and _stream.encoding.lower() not in ("utf-8", "utf8")
15
+ ):
16
+ try:
17
+ _stream.reconfigure(encoding="utf-8", errors="replace")
18
+ except AttributeError:
19
+ pass
20
+
21
+ import click
22
+
23
+ from .commands.autocomplete import autocomplete_group
24
+ from .commands.listings import listings
25
+ from .commands.search import search
26
+ from .core.exceptions import AirbnbError
27
+ from .utils.repl_skin import ReplSkin
28
+
29
+ _skin = ReplSkin("airbnb", version="0.1.0")
30
+
31
+
32
+ @click.group(invoke_without_command=True)
33
+ @click.option("--json", "json_mode", is_flag=True, help="Output all results as JSON.")
34
+ @click.option("--version", is_flag=True, help="Show version and exit.")
35
+ @click.pass_context
36
+ def cli(ctx, json_mode, version):
37
+ """🏠 cli-web-airbnb — Search Airbnb stays, listings and locations.
38
+
39
+ Running without a subcommand enters interactive REPL mode.
40
+
41
+ Examples:
42
+ cli-web-airbnb search stays "London, UK"
43
+ cli-web-airbnb search stays "Paris, France" --checkin 2024-06-01 --checkout 2024-06-05 --json
44
+ cli-web-airbnb listings get 770993223449115417
45
+ cli-web-airbnb autocomplete locations "New Yor"
46
+ """
47
+ ctx.ensure_object(dict)
48
+ ctx.obj["json"] = json_mode
49
+
50
+ if version:
51
+ click.echo("cli-web-airbnb 0.1.0")
52
+ return
53
+
54
+ if ctx.invoked_subcommand is None:
55
+ _repl(ctx)
56
+
57
+
58
+ cli.add_command(search)
59
+ cli.add_command(listings)
60
+ cli.add_command(autocomplete_group, name="autocomplete")
61
+
62
+
63
+ def _print_repl_help() -> None:
64
+ """Print REPL help listing all commands and key options."""
65
+ _skin.info("Available commands:")
66
+ print()
67
+ print(" search stays LOCATION Search for stays in a location")
68
+ print(" --checkin DATE Check-in date (YYYY-MM-DD)")
69
+ print(" --checkout DATE Check-out date (YYYY-MM-DD)")
70
+ print(" --adults N Number of adults (default: 1)")
71
+ print(" --children N Number of children")
72
+ print(" --infants N Number of infants")
73
+ print(" --pets N Number of pets (0 or 1)")
74
+ print(" --min-price N Minimum nightly price")
75
+ print(" --max-price N Maximum nightly price")
76
+ print(" --room-type TYPE Filter: entire_home|private_room|shared_room|hotel_room")
77
+ print(
78
+ " --amenity ID Filter by amenity ID (repeatable: 4=WiFi, 8=Kitchen, 40=AC, 33=Pool)"
79
+ )
80
+ print(" --cursor TOKEN Pagination cursor for next page")
81
+ print(" --page N Page number (note: Airbnb uses cursors, not pages)")
82
+ print(" --locale TEXT Language locale (default: en)")
83
+ print(" --currency TEXT Currency code (default: USD)")
84
+ print()
85
+ print(" listings get LISTING_ID Get details for a specific listing")
86
+ print(" --adults N Number of adults")
87
+ print(" --checkin DATE Check-in date")
88
+ print(" --checkout DATE Check-out date")
89
+ print(" --locale TEXT Language locale (default: en)")
90
+ print(" --currency TEXT Currency code (default: USD)")
91
+ print()
92
+ print(" listings reviews LISTING_ID Get guest reviews for a listing")
93
+ print(" --limit N Number of reviews (default: 24)")
94
+ print(" --offset N Pagination offset (default: 0)")
95
+ print(" --sort ORDER Sort: BEST_QUALITY|RECENT|RATING_DESC|RATING_ASC")
96
+ print()
97
+ print(" listings availability LISTING_ID Get 12-month availability calendar")
98
+ print(" --month N Starting month 1-12 (default: current)")
99
+ print(" --year N Starting year (default: current)")
100
+ print(" --count N Number of months to fetch (default: 12)")
101
+ print()
102
+ print(" autocomplete locations QUERY Suggest locations for a partial query")
103
+ print(" --num-results N Number of suggestions (default: 5)")
104
+ print()
105
+ print(" Global flags:")
106
+ print(" --json Output results as JSON (place before OR after subcommand)")
107
+ print(' Examples: --json search stays "London" OR: search stays "London" --json')
108
+ print()
109
+ print(" help Show this help")
110
+ print(" quit / exit Exit REPL")
111
+ print()
112
+
113
+
114
+ def _repl(ctx: click.Context) -> None:
115
+ """Interactive REPL loop."""
116
+ _skin.print_banner()
117
+ pt_session = _skin.create_prompt_session()
118
+
119
+ while True:
120
+ try:
121
+ line = _skin.get_input(pt_session)
122
+ except (KeyboardInterrupt, EOFError):
123
+ break
124
+
125
+ line = line.strip()
126
+ if not line:
127
+ continue
128
+ if line in ("help", "?", "h"):
129
+ _print_repl_help()
130
+ continue
131
+ if line in ("quit", "exit", "q"):
132
+ break
133
+
134
+ try:
135
+ args = shlex.split(line)
136
+ except ValueError as exc:
137
+ click.echo(f"Parse error: {exc}", err=True)
138
+ continue
139
+
140
+ repl_args = ["--json"] + args if ctx.obj.get("json") else args
141
+ try:
142
+ cli.main(args=repl_args, standalone_mode=False)
143
+ except SystemExit:
144
+ pass
145
+ except Exception as exc:
146
+ if ctx.obj.get("json"):
147
+ payload = (
148
+ exc.to_dict()
149
+ if isinstance(exc, AirbnbError)
150
+ else {"error": True, "code": "ERROR", "message": str(exc)}
151
+ )
152
+ click.echo(_json.dumps(payload))
153
+ else:
154
+ click.echo(f"Error: {exc}", err=True)
155
+
156
+
157
+ def main() -> None:
158
+ """Entry point for cli-web-airbnb."""
159
+ cli()
160
+
161
+
162
+ # MCP server mode — exposes every command as an MCP tool over stdio.
163
+ # Canonical adapter: cli-web-core/cli_web_core/mcp_server.py (vendored copy).
164
+ from cli_web.airbnb import __version__ as _pkg_version # noqa: E402
165
+ from cli_web.airbnb.utils.doctor import register_doctor_command # noqa: E402
166
+ from cli_web.airbnb.utils.mcp_server import register_mcp_command # noqa: E402
167
+
168
+ register_mcp_command(cli, app_name="airbnb", version=_pkg_version)
169
+ register_doctor_command(cli, app_name="airbnb", pkg="airbnb")
@@ -0,0 +1,100 @@
1
+ """Autocomplete command group — location suggestions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from ..core.client import AirbnbClient
8
+ from ..utils.helpers import handle_errors, print_json, resolve_json_mode
9
+
10
+
11
+ @click.group(
12
+ "autocomplete",
13
+ invoke_without_command=True,
14
+ )
15
+ @click.pass_context
16
+ def autocomplete_group(ctx: click.Context) -> None:
17
+ """Autocomplete helpers for Airbnb search inputs (locations, etc.)."""
18
+ if ctx.invoked_subcommand is None:
19
+ click.echo(ctx.get_help())
20
+
21
+
22
+ @autocomplete_group.command("locations")
23
+ @click.argument("query")
24
+ @click.option(
25
+ "--num-results",
26
+ "-n",
27
+ default=5,
28
+ show_default=True,
29
+ type=int,
30
+ help="Maximum number of suggestions to return.",
31
+ )
32
+ @click.option(
33
+ "--locale",
34
+ default="en",
35
+ show_default=True,
36
+ help="Locale for suggestion labels (e.g. en, fr, de).",
37
+ )
38
+ @click.option(
39
+ "--currency",
40
+ default="USD",
41
+ show_default=True,
42
+ help="Currency code used for price display context (e.g. USD, EUR).",
43
+ )
44
+ @click.option(
45
+ "--json",
46
+ "json_mode",
47
+ is_flag=True,
48
+ default=False,
49
+ help="Output as JSON.",
50
+ )
51
+ @click.pass_context
52
+ def locations(
53
+ ctx: click.Context,
54
+ query: str,
55
+ num_results: int,
56
+ locale: str,
57
+ currency: str,
58
+ json_mode: bool,
59
+ ) -> None:
60
+ """Suggest locations matching partial QUERY text.
61
+
62
+ Returns ranked location suggestions suitable for use as a destination
63
+ in search commands.
64
+
65
+ \b
66
+ Examples:
67
+ autocomplete locations "Lond"
68
+ autocomplete locations "New Y" --num-results 10
69
+ autocomplete locations "Paris" --locale fr --currency EUR --json
70
+ """
71
+ json_mode = resolve_json_mode(json_mode, ctx)
72
+ with handle_errors(json_mode=json_mode):
73
+ with AirbnbClient(locale=locale, currency=currency) as client:
74
+ suggestions = client.autocomplete_locations(
75
+ query=query,
76
+ num_results=num_results,
77
+ )
78
+
79
+ if not suggestions:
80
+ if json_mode:
81
+ print_json({"success": True, "data": {"query": query, "suggestions": []}})
82
+ else:
83
+ click.echo(f"No location suggestions found for '{query}'.")
84
+ return
85
+
86
+ if json_mode:
87
+ print_json(
88
+ {
89
+ "success": True,
90
+ "data": {
91
+ "query": query,
92
+ "suggestions": [s.to_dict() for s in suggestions],
93
+ },
94
+ }
95
+ )
96
+ else:
97
+ click.echo(f"\nLocation suggestions for '{query}':\n")
98
+ for i, suggestion in enumerate(suggestions, start=1):
99
+ click.echo(f" {i}. {suggestion.display} [place_id: {suggestion.place_id}]")
100
+ click.echo()