walla-cli 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.
Files changed (75) hide show
  1. walla_cli-0.1.0/.cursor/rules/quality.mdc +14 -0
  2. walla_cli-0.1.0/.github/workflows/ci.yml +26 -0
  3. walla_cli-0.1.0/.gitignore +20 -0
  4. walla_cli-0.1.0/AGENTS.md +63 -0
  5. walla_cli-0.1.0/LICENSE +21 -0
  6. walla_cli-0.1.0/PKG-INFO +204 -0
  7. walla_cli-0.1.0/README.md +160 -0
  8. walla_cli-0.1.0/docs/WIRE.md +60 -0
  9. walla_cli-0.1.0/docs/assets/walla-github-banner.jpg +0 -0
  10. walla_cli-0.1.0/docs/code_quality.md +80 -0
  11. walla_cli-0.1.0/docs/data_engineering_standards.md +87 -0
  12. walla_cli-0.1.0/docs/mcp.md +24 -0
  13. walla_cli-0.1.0/docs/objective_function.md +7 -0
  14. walla_cli-0.1.0/docs/rate_limit_analysis.md +28 -0
  15. walla_cli-0.1.0/fixtures/categories.json +49 -0
  16. walla_cli-0.1.0/fixtures/item_detail.json +94 -0
  17. walla_cli-0.1.0/fixtures/search_bicicleta.json +182 -0
  18. walla_cli-0.1.0/pyproject.toml +96 -0
  19. walla_cli-0.1.0/scripts/check_code_quality.py +124 -0
  20. walla_cli-0.1.0/scripts/probe_rate.py +48 -0
  21. walla_cli-0.1.0/skills/walla/SKILL.md +30 -0
  22. walla_cli-0.1.0/tests/test_account.py +128 -0
  23. walla_cli-0.1.0/tests/test_account_more.py +185 -0
  24. walla_cli-0.1.0/tests/test_catch.py +60 -0
  25. walla_cli-0.1.0/tests/test_cli_json.py +156 -0
  26. walla_cli-0.1.0/tests/test_core.py +47 -0
  27. walla_cli-0.1.0/tests/test_edges.py +157 -0
  28. walla_cli-0.1.0/tests/test_http_more.py +124 -0
  29. walla_cli-0.1.0/tests/test_live.py +68 -0
  30. walla_cli-0.1.0/tests/test_parsers.py +38 -0
  31. walla_cli-0.1.0/tests/test_polite.py +88 -0
  32. walla_cli-0.1.0/tests/test_search_api.py +49 -0
  33. walla_cli-0.1.0/tests/test_session_store.py +55 -0
  34. walla_cli-0.1.0/tests/test_verdict.py +49 -0
  35. walla_cli-0.1.0/walla/__init__.py +7 -0
  36. walla_cli-0.1.0/walla/account/__init__.py +23 -0
  37. walla_cli-0.1.0/walla/account/actions.py +135 -0
  38. walla_cli-0.1.0/walla/account/cookies.py +88 -0
  39. walla_cli-0.1.0/walla/account/inbox.py +147 -0
  40. walla_cli-0.1.0/walla/account/login.py +205 -0
  41. walla_cli-0.1.0/walla/account/prompt.py +56 -0
  42. walla_cli-0.1.0/walla/account/session_store.py +36 -0
  43. walla_cli-0.1.0/walla/cli/__init__.py +7 -0
  44. walla_cli-0.1.0/walla/cli/banner.py +33 -0
  45. walla_cli-0.1.0/walla/cli/catch.py +43 -0
  46. walla_cli-0.1.0/walla/cli/cmd_account.py +162 -0
  47. walla_cli-0.1.0/walla/cli/cmd_look.py +143 -0
  48. walla_cli-0.1.0/walla/cli/cmd_ops.py +100 -0
  49. walla_cli-0.1.0/walla/cli/console.py +9 -0
  50. walla_cli-0.1.0/walla/cli/main.py +44 -0
  51. walla_cli-0.1.0/walla/core/__init__.py +12 -0
  52. walla_cli-0.1.0/walla/core/envelope.py +46 -0
  53. walla_cli-0.1.0/walla/core/errors.py +90 -0
  54. walla_cli-0.1.0/walla/core/exceptions.py +67 -0
  55. walla_cli-0.1.0/walla/core/human_fix.py +26 -0
  56. walla_cli-0.1.0/walla/core/instruct.py +50 -0
  57. walla_cli-0.1.0/walla/core/path.py +32 -0
  58. walla_cli-0.1.0/walla/http/__init__.py +14 -0
  59. walla_cli-0.1.0/walla/http/client.py +169 -0
  60. walla_cli-0.1.0/walla/http/headers.py +36 -0
  61. walla_cli-0.1.0/walla/http/polite.py +108 -0
  62. walla_cli-0.1.0/walla/hunter/__init__.py +18 -0
  63. walla_cli-0.1.0/walla/hunter/profile_store.py +56 -0
  64. walla_cli-0.1.0/walla/hunter/verdict.py +42 -0
  65. walla_cli-0.1.0/walla/hunter/watches.py +60 -0
  66. walla_cli-0.1.0/walla/mcp/__init__.py +7 -0
  67. walla_cli-0.1.0/walla/mcp/_entry.py +17 -0
  68. walla_cli-0.1.0/walla/mcp/server.py +135 -0
  69. walla_cli-0.1.0/walla/models/__init__.py +18 -0
  70. walla_cli-0.1.0/walla/models/account.py +48 -0
  71. walla_cli-0.1.0/walla/models/listing.py +51 -0
  72. walla_cli-0.1.0/walla/models/profile.py +37 -0
  73. walla_cli-0.1.0/walla/search/__init__.py +15 -0
  74. walla_cli-0.1.0/walla/search/api.py +78 -0
  75. walla_cli-0.1.0/walla/search/parse.py +150 -0
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Extreme quality bar for walla (Korotkevich + Gray)
3
+ alwaysApply: true
4
+ ---
5
+
6
+ Before editing `walla/`, read:
7
+
8
+ - `docs/code_quality.md` (Korotkevich / Tourist bar)
9
+ - `docs/data_engineering_standards.md` (Gray / Stonebraker bar)
10
+ - `docs/WIRE.md`
11
+
12
+ Enforce: downward DAG, <=250 LOC/file, no import side effects, `mypy --strict`,
13
+ no password/token in logs, fixture + parser test for wire changes.
14
+ Run `python scripts/check_code_quality.py` and `pytest -q -m "not live"`.
@@ -0,0 +1,26 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - name: Install
17
+ run: |
18
+ python -m pip install -U pip
19
+ pip install -e ".[dev,mcp]"
20
+ - name: Quality
21
+ run: |
22
+ python scripts/check_code_quality.py
23
+ ruff check .
24
+ mypy walla scripts/check_code_quality.py
25
+ - name: Tests
26
+ run: pytest -q -m "not live" --cov=walla --cov-fail-under=90
@@ -0,0 +1,20 @@
1
+ .env
2
+ .env.*
3
+ *.pyc
4
+ __pycache__/
5
+ .venv/
6
+ venv/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ htmlcov/
12
+ dist/
13
+ build/
14
+ *.egg-info/
15
+ session.json
16
+ **/session.json
17
+ cookies.json
18
+ cookies.txt
19
+ *.cookies
20
+ .DS_Store
@@ -0,0 +1,63 @@
1
+ # AGENTS.md — how to extend `walla`
2
+
3
+ You are working on **walla**: Wallapop.es CLI / library / MCP (look · offer · talk).
4
+
5
+ ## Quality (read first)
6
+
7
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar
8
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar
9
+ - [`docs/WIRE.md`](docs/WIRE.md) — captured wire
10
+ - [`docs/objective_function.md`](docs/objective_function.md)
11
+
12
+ Run before shipping:
13
+
14
+ ```bash
15
+ python scripts/check_code_quality.py
16
+ ruff check .
17
+ mypy walla
18
+ pytest -q -m "not live"
19
+ ```
20
+
21
+ ## Calling walla from an agent
22
+
23
+ ```bash
24
+ pipx install 'walla-cli[mcp]'
25
+ walla instruct --json
26
+ walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona --json
27
+ walla login --cookie '<session-token>' --json
28
+ walla search "tabla" --json
29
+ ```
30
+
31
+ Never print session cookies or bearer tokens. On `error_type: auth`, ask the human
32
+ to run `walla login` and paste `__Secure-next-auth.session-token`.
33
+
34
+ ## Architecture map
35
+
36
+ | Path | Owns | Must not |
37
+ |------|------|----------|
38
+ | `walla/models/` | Pydantic DTOs | I/O |
39
+ | `walla/http/` | curl_cffi, polite | CLI/MCP imports |
40
+ | `walla/core/` | Envelope, errors, paths, human_fix | Live Wallapop calls (except doctor via CLI) |
41
+ | `walla/search/` | Public look | Account mutations |
42
+ | `walla/account/` | Session, inbox, say, offer, fav | HTML scrape |
43
+ | `walla/hunter/` | Profile, watches, GRAB/LOOK/PASS | HTTP |
44
+ | `walla/cli/` | Typer + Rich + `--json` | Import MCP |
45
+ | `walla/mcp/` | FastMCP | Import CLI |
46
+
47
+ Ranks: models 10 → exceptions 15 → http 20 → core 30 → search/account/hunter 40 → cli/mcp 50.
48
+
49
+ ## Live capture workflow
50
+
51
+ 1. Reproduce in browser Network on `es.wallapop.com`.
52
+ 2. Call with `curl_cffi` + `X-DeviceOS: 0`.
53
+ 3. Redact secrets → `fixtures/` → parser test → update `docs/WIRE.md`.
54
+ 4. Never HTML-scrape as the primary path. Never Playwright.
55
+
56
+ ## Do / don't
57
+
58
+ - **Do** keep files <= 250 LOC.
59
+ - **Do** fail closed; typed `error_type`.
60
+ - **Do** require `--yes` for say/offer.
61
+ - **Don't** commit session.json, cookies, or tokens.
62
+ - **Don't** invent wire shapes without a fixture.
63
+ - **Don't** require `.env` credentials; login is cookie paste.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dr. Berend Gort
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.5
2
+ Name: walla-cli
3
+ Version: 0.1.0
4
+ Summary: Wallapop.es CLI - look · offer · talk for humans and agents
5
+ Project-URL: Homepage, https://github.com/berendgort/wallapop-cli
6
+ Project-URL: Repository, https://github.com/berendgort/wallapop-cli
7
+ Project-URL: Issues, https://github.com/berendgort/wallapop-cli/issues
8
+ Author-email: "Dr. Berend Gort" <berend.gort@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,marketplace,mcp,spain,wallapop
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: curl-cffi>=0.7.0
22
+ Requires-Dist: pydantic>=2.10.0
23
+ Requires-Dist: rich>=13.8.0
24
+ Requires-Dist: tenacity>=9.0.0
25
+ Requires-Dist: typer>=0.15.1
26
+ Provides-Extra: all
27
+ Requires-Dist: build>=1.2.0; extra == 'all'
28
+ Requires-Dist: fastmcp>=3.2; extra == 'all'
29
+ Requires-Dist: mcp>=1.2.0; extra == 'all'
30
+ Requires-Dist: mypy>=1.14.0; extra == 'all'
31
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'all'
32
+ Requires-Dist: pytest>=8.3.4; extra == 'all'
33
+ Requires-Dist: ruff>=0.8.4; extra == 'all'
34
+ Provides-Extra: dev
35
+ Requires-Dist: build>=1.2.0; extra == 'dev'
36
+ Requires-Dist: mypy>=1.14.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
38
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
39
+ Requires-Dist: ruff>=0.8.4; extra == 'dev'
40
+ Provides-Extra: mcp
41
+ Requires-Dist: fastmcp>=3.2; extra == 'mcp'
42
+ Requires-Dist: mcp>=1.2.0; extra == 'mcp'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # walla
46
+
47
+ ```
48
+ ██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗
49
+ ██║ ██║ ██╔══██╗ ██║ ██║ ██╔══██╗ ██╔════╝ ██║ ██║
50
+ ██║ █╗ ██║ ███████║ ██║ ██║ ███████║ █████╗ ██║ ██║ ██║
51
+ ██║███╗██║ ██╔══██║ ██║ ██║ ██╔══██║ ╚════╝ ██║ ██║ ██║
52
+ ╚███╔███╔╝ ██║ ██║ ███████╗ ███████╗ ██║ ██║ ╚██████╗ ███████╗ ██║
53
+ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
54
+ look · offer · talk
55
+ Dr. Berend Gort · www.berendgort.dev
56
+ ```
57
+
58
+ ![walla banner](docs/assets/walla-github-banner.jpg)
59
+
60
+ **walla** is an unofficial Wallapop.es **CLI + MCP + Python library** for Cursor
61
+ agents and humans: search listings, read inbox, send a message, make an offer.
62
+
63
+ Not affiliated with Wallapop. Respect ToS and rate limits.
64
+
65
+ ### What this unlocks
66
+
67
+ You tell Cursor: *“Find me a used kite near Barcelona under €400, message
68
+ the seller, and offer if it’s a GRAB.”*
69
+
70
+ The agent runs `walla` — ranks listings **GRAB / LOOK / PASS**, links you the
71
+ item, asks before sending, then talks and offers on your behalf:
72
+
73
+ ```bash
74
+ walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona --budget 400
75
+ walla search "tabla kite Cabrinha" --max 40 --json
76
+ # agent: GRAB · Cabrinha Moto 10m · €320 · 4 km
77
+ # https://es.wallapop.com/item/...
78
+
79
+ walla login # paste session cookie once
80
+ walla say <id> "Hola, ¿sigue disponible? Puedo pasar hoy." --yes
81
+ walla offer <id> --eur 280 --yes # prints total; needs your confirm
82
+ walla inbox --json # catch the reply
83
+ ```
84
+
85
+ That is the product: a Cursor agent that can **look, talk, and offer** on
86
+ Wallapop without opening the website for every step.
87
+
88
+ **Quality bar (enforced in CI):**
89
+
90
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar
91
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar
92
+ - [`docs/WIRE.md`](docs/WIRE.md) — captured consumer API
93
+ - [`docs/rate_limit_analysis.md`](docs/rate_limit_analysis.md) — polite spacing
94
+
95
+ ---
96
+
97
+ ## For AI agents (mandatory protocol)
98
+
99
+ If the user pasted this GitHub link (or this README), **follow this section exactly**.
100
+
101
+ ### Goal
102
+
103
+ Look, offer, and talk on wallapop.es using the `walla` package. Speak briefly.
104
+ Link listings as `https://es.wallapop.com/item/<web_slug>`. Verdicts:
105
+ **GRAB / LOOK / PASS**.
106
+
107
+ ### Bootstrap
108
+
109
+ 1. Install from this clone:
110
+ ```bash
111
+ pipx install 'walla-cli[mcp]'
112
+ # from clone: pipx install -e '.[mcp]'
113
+ ```
114
+ 2. `walla doctor --json` — read `auth.session` and `network.reachable`.
115
+ **Never** print tokens or cookies.
116
+ 3. If no session: ask the human to run `walla login` and paste
117
+ `__Secure-next-auth.session-token` from es.wallapop.com (Cookie-Editor).
118
+ Agents may pass it non-interactively: `walla login --cookie '<value>' --json`.
119
+ 4. Do the ask with `walla … --json` and narrate in prose.
120
+
121
+ Optional MCP:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "walla": { "command": "walla-mcp" }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Operating loop
132
+
133
+ | Ask | Command |
134
+ |-----|---------|
135
+ | Look | `walla search "tabla kite" --max 40 --json` then `walla item <id> --json` |
136
+ | Talk | `walla inbox --json` · `walla thread <id> --json` · `walla say <id> "…" --yes` |
137
+ | Offer | `walla offer <id> --eur 180 --yes` |
138
+
139
+ Rules:
140
+
141
+ - Reads are safe. `say` / `offer` need `--yes` / `confirm=true`. Name listing + euros.
142
+ - If they only asked to look, do not send.
143
+ - Ambiguous match: ask once with candidates.
144
+ - In-person offers outside pickup radius: refuse.
145
+ - Envelope: `ok`, `api_version`, `data` or `error` / `error_type` / `retryable`.
146
+
147
+ `walla instruct --json` returns this recipe.
148
+
149
+ ---
150
+
151
+ ## Install
152
+
153
+ ```bash
154
+ git clone git@github.com:berendgort/wallapop-cli.git
155
+ cd wallapop-cli
156
+ pipx install 'walla-cli[mcp]' # or: pipx install -e '.[mcp]'
157
+ walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona --json
158
+ walla login # paste __Secure-next-auth.session-token
159
+ walla doctor --json
160
+ ```
161
+
162
+ ## CLI
163
+
164
+ | Command | Role |
165
+ |---------|------|
166
+ | `walla setup` / `profile` | Home point, radius, budget |
167
+ | `walla search` / `item` / `categories` | Look |
168
+ | `walla watch add\|list\|check` | Local saved searches |
169
+ | `walla login` / `logout` / `whoami` | Session (paste cookie; optional `--password`) |
170
+ | `walla inbox` / `thread` / `say` | Talk |
171
+ | `walla offer` | Offer (prints total, needs `--yes`) |
172
+ | `walla fav list\|add\|rm` | Favorites |
173
+ | `walla instruct` / `doctor` | Agent recipe + health |
174
+
175
+ ## Develop
176
+
177
+ ```bash
178
+ pip install -e '.[dev,mcp]'
179
+ pytest -q -m 'not live'
180
+ pytest -m live # hits Wallapop; needs network
181
+ ruff check .
182
+ mypy walla
183
+ python scripts/check_code_quality.py
184
+ ```
185
+
186
+ | Layer | Path | Role |
187
+ |-------|------|------|
188
+ | Models | `walla/models/` | Pydantic only |
189
+ | HTTP | `walla/http/` | curl_cffi + polite |
190
+ | Core | `walla/core/` | Envelope, errors, human_fix |
191
+ | Search | `walla/search/` | Look |
192
+ | Account | `walla/account/` | Session, inbox, offer |
193
+ | Hunter | `walla/hunter/` | Profile, watches, verdicts |
194
+ | CLI / MCP | `walla/cli/` · `walla/mcp/` | Entrypoints |
195
+
196
+ Read [`AGENTS.md`](AGENTS.md) before extending.
197
+
198
+ ## Disclaimer
199
+
200
+ Unofficial. Not affiliated with Wallapop. Personal / research use.
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,160 @@
1
+ # walla
2
+
3
+ ```
4
+ ██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗
5
+ ██║ ██║ ██╔══██╗ ██║ ██║ ██╔══██╗ ██╔════╝ ██║ ██║
6
+ ██║ █╗ ██║ ███████║ ██║ ██║ ███████║ █████╗ ██║ ██║ ██║
7
+ ██║███╗██║ ██╔══██║ ██║ ██║ ██╔══██║ ╚════╝ ██║ ██║ ██║
8
+ ╚███╔███╔╝ ██║ ██║ ███████╗ ███████╗ ██║ ██║ ╚██████╗ ███████╗ ██║
9
+ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
10
+ look · offer · talk
11
+ Dr. Berend Gort · www.berendgort.dev
12
+ ```
13
+
14
+ ![walla banner](docs/assets/walla-github-banner.jpg)
15
+
16
+ **walla** is an unofficial Wallapop.es **CLI + MCP + Python library** for Cursor
17
+ agents and humans: search listings, read inbox, send a message, make an offer.
18
+
19
+ Not affiliated with Wallapop. Respect ToS and rate limits.
20
+
21
+ ### What this unlocks
22
+
23
+ You tell Cursor: *“Find me a used kite near Barcelona under €400, message
24
+ the seller, and offer if it’s a GRAB.”*
25
+
26
+ The agent runs `walla` — ranks listings **GRAB / LOOK / PASS**, links you the
27
+ item, asks before sending, then talks and offers on your behalf:
28
+
29
+ ```bash
30
+ walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona --budget 400
31
+ walla search "tabla kite Cabrinha" --max 40 --json
32
+ # agent: GRAB · Cabrinha Moto 10m · €320 · 4 km
33
+ # https://es.wallapop.com/item/...
34
+
35
+ walla login # paste session cookie once
36
+ walla say <id> "Hola, ¿sigue disponible? Puedo pasar hoy." --yes
37
+ walla offer <id> --eur 280 --yes # prints total; needs your confirm
38
+ walla inbox --json # catch the reply
39
+ ```
40
+
41
+ That is the product: a Cursor agent that can **look, talk, and offer** on
42
+ Wallapop without opening the website for every step.
43
+
44
+ **Quality bar (enforced in CI):**
45
+
46
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar
47
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar
48
+ - [`docs/WIRE.md`](docs/WIRE.md) — captured consumer API
49
+ - [`docs/rate_limit_analysis.md`](docs/rate_limit_analysis.md) — polite spacing
50
+
51
+ ---
52
+
53
+ ## For AI agents (mandatory protocol)
54
+
55
+ If the user pasted this GitHub link (or this README), **follow this section exactly**.
56
+
57
+ ### Goal
58
+
59
+ Look, offer, and talk on wallapop.es using the `walla` package. Speak briefly.
60
+ Link listings as `https://es.wallapop.com/item/<web_slug>`. Verdicts:
61
+ **GRAB / LOOK / PASS**.
62
+
63
+ ### Bootstrap
64
+
65
+ 1. Install from this clone:
66
+ ```bash
67
+ pipx install 'walla-cli[mcp]'
68
+ # from clone: pipx install -e '.[mcp]'
69
+ ```
70
+ 2. `walla doctor --json` — read `auth.session` and `network.reachable`.
71
+ **Never** print tokens or cookies.
72
+ 3. If no session: ask the human to run `walla login` and paste
73
+ `__Secure-next-auth.session-token` from es.wallapop.com (Cookie-Editor).
74
+ Agents may pass it non-interactively: `walla login --cookie '<value>' --json`.
75
+ 4. Do the ask with `walla … --json` and narrate in prose.
76
+
77
+ Optional MCP:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "walla": { "command": "walla-mcp" }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Operating loop
88
+
89
+ | Ask | Command |
90
+ |-----|---------|
91
+ | Look | `walla search "tabla kite" --max 40 --json` then `walla item <id> --json` |
92
+ | Talk | `walla inbox --json` · `walla thread <id> --json` · `walla say <id> "…" --yes` |
93
+ | Offer | `walla offer <id> --eur 180 --yes` |
94
+
95
+ Rules:
96
+
97
+ - Reads are safe. `say` / `offer` need `--yes` / `confirm=true`. Name listing + euros.
98
+ - If they only asked to look, do not send.
99
+ - Ambiguous match: ask once with candidates.
100
+ - In-person offers outside pickup radius: refuse.
101
+ - Envelope: `ok`, `api_version`, `data` or `error` / `error_type` / `retryable`.
102
+
103
+ `walla instruct --json` returns this recipe.
104
+
105
+ ---
106
+
107
+ ## Install
108
+
109
+ ```bash
110
+ git clone git@github.com:berendgort/wallapop-cli.git
111
+ cd wallapop-cli
112
+ pipx install 'walla-cli[mcp]' # or: pipx install -e '.[mcp]'
113
+ walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona --json
114
+ walla login # paste __Secure-next-auth.session-token
115
+ walla doctor --json
116
+ ```
117
+
118
+ ## CLI
119
+
120
+ | Command | Role |
121
+ |---------|------|
122
+ | `walla setup` / `profile` | Home point, radius, budget |
123
+ | `walla search` / `item` / `categories` | Look |
124
+ | `walla watch add\|list\|check` | Local saved searches |
125
+ | `walla login` / `logout` / `whoami` | Session (paste cookie; optional `--password`) |
126
+ | `walla inbox` / `thread` / `say` | Talk |
127
+ | `walla offer` | Offer (prints total, needs `--yes`) |
128
+ | `walla fav list\|add\|rm` | Favorites |
129
+ | `walla instruct` / `doctor` | Agent recipe + health |
130
+
131
+ ## Develop
132
+
133
+ ```bash
134
+ pip install -e '.[dev,mcp]'
135
+ pytest -q -m 'not live'
136
+ pytest -m live # hits Wallapop; needs network
137
+ ruff check .
138
+ mypy walla
139
+ python scripts/check_code_quality.py
140
+ ```
141
+
142
+ | Layer | Path | Role |
143
+ |-------|------|------|
144
+ | Models | `walla/models/` | Pydantic only |
145
+ | HTTP | `walla/http/` | curl_cffi + polite |
146
+ | Core | `walla/core/` | Envelope, errors, human_fix |
147
+ | Search | `walla/search/` | Look |
148
+ | Account | `walla/account/` | Session, inbox, offer |
149
+ | Hunter | `walla/hunter/` | Profile, watches, verdicts |
150
+ | CLI / MCP | `walla/cli/` · `walla/mcp/` | Entrypoints |
151
+
152
+ Read [`AGENTS.md`](AGENTS.md) before extending.
153
+
154
+ ## Disclaimer
155
+
156
+ Unofficial. Not affiliated with Wallapop. Personal / research use.
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,60 @@
1
+ # Wire capture — Wallapop consumer API
2
+
3
+ Unofficial. Captured against `es.wallapop.com` / `api.wallapop.com` (2026-10-04).
4
+ Re-verify before trusting after a long gap.
5
+
6
+ ## Headers (public)
7
+
8
+ Required: `X-DeviceOS: 0`. Browser `User-Agent` + `Origin`/`Referer` of
9
+ `https://es.wallapop.com` work. Do not invent an `X-Signature` for search.
10
+
11
+ ## Search
12
+
13
+ `GET https://api.wallapop.com/api/v3/search`
14
+
15
+ Params: `source=search_box`, `keywords`, `latitude`, `longitude`, `order_by`
16
+ (`most_relevance` | `newest` | `closest` | price variants), optional
17
+ `min_sale_price`, `max_sale_price`, `category_id`, `next_page`.
18
+
19
+ Response: `data.section.payload.items[]` (40/page), `meta.next_page` opaque JWT.
20
+ Item fields include `id`, `title`, `description`, `price.amount`, `web_slug`,
21
+ `location`, `reserved.flag`, `shipping`, `created_at`.
22
+
23
+ Public URL: `https://es.wallapop.com/item/<web_slug>`.
24
+
25
+ ## Item
26
+
27
+ `GET https://api.wallapop.com/api/v3/items/{id}` — detail with
28
+ `title.original`, `description.original`, `slug`, `share_url`, `user`, counters.
29
+
30
+ ## Categories
31
+
32
+ `GET https://api.wallapop.com/api/v3/categories` — top-level tree.
33
+
34
+ ## Auth
35
+
36
+ `POST https://api.wallapop.com/api/v3/access/login` with
37
+ `{"username","password"}` — live probe returned **HTTP 400 empty body**
38
+ (`x-wallapop-service: auth`) for the configured account. Treat as MFA /
39
+ Keycloak. Fallback: import `__Secure-next-auth.session-token` and mint via
40
+ `GET https://es.wallapop.com/api/auth/session` → `{ token }`.
41
+
42
+ Refresh: `POST /api/v3/access/refresh` with `{ refresh_token }` when password
43
+ login ever succeeds.
44
+
45
+ ## Messaging / offers (auth)
46
+
47
+ Reported shapes (confirm with a live token before trusting writes):
48
+
49
+ - Inbox: `GET /api/v3/conversations`
50
+ - Messages: `GET|POST /api/v3/conversations/{id}/messages`
51
+ - Offers: `POST /api/v3/delivery/buyer/offers`
52
+ - Favorites: `/api/v3/users/me/favorites`, `/api/v3/items/{id}/favorite`
53
+
54
+ Writes that fail closed as `error_type: unsupported` until fixtures exist.
55
+
56
+ ## Rate limits
57
+
58
+ Default spacing: `WALLA_MIN_INTERVAL=1.0` s. Honor `Retry-After` on 429.
59
+ Repeated 403 opens a circuit (`WALLA_CIRCUIT_TTL`). See
60
+ [`rate_limit_analysis.md`](rate_limit_analysis.md).
@@ -0,0 +1,80 @@
1
+ # Engineering Standard: The Korotkevich / Tourist Bar
2
+
3
+ Every line in `walla` must meet a competitive-programming bar: mathematical
4
+ clarity, modular purity, machine readability. Sprawling monoliths, circular
5
+ imports, hidden I/O on import, and ambiguous contracts are forbidden.
6
+
7
+ Automated enforcement: `python scripts/check_code_quality.py`
8
+
9
+ ---
10
+
11
+ ## 1. Architectural Layers & Dependency Hierarchy
12
+
13
+ Strict downward DAG. Lower ranks never import higher ranks. Cycles are fatal.
14
+
15
+ ```
16
+ [10] walla.models Pydantic DTOs only
17
+ [20] walla.http curl_cffi client, polite spacing, headers
18
+ [30] walla.core Envelope, errors, paths, human_fix
19
+ [40] walla.search Public look (search / item / categories)
20
+ [40] walla.account Session, inbox, say, offer, favorites
21
+ [40] walla.hunter Profile, watches, GRAB / LOOK / PASS
22
+ [50] walla.cli · walla.mcp Typer + FastMCP entrypoints
23
+ ```
24
+
25
+ ### Invariants
26
+
27
+ - **Zero Circular Dependencies**: No cycles at module or package level.
28
+ - **Downward Imports Only**: Higher layers may import lower; never reverse
29
+ (e.g. `search` never imports `cli`, `models` never imports `core`).
30
+ - **Zero Import Side Effects**: No network, disk profile I/O, or client
31
+ construction on import.
32
+ - **Facades**: Package `__init__.py` files stay thin re-exports.
33
+ - Use `if TYPE_CHECKING:` for type-only upward references.
34
+
35
+ ---
36
+
37
+ ## 2. Hard File Size Cap: <= 250 Lines of Code
38
+
39
+ No file in `walla/` exceeds **250 lines**. When a module approaches the limit,
40
+ split into single-responsibility submodules behind an `__init__.py` facade.
41
+
42
+ ---
43
+
44
+ ## 3. Functional Core, Imperative Shell
45
+
46
+ - Pure parsers, verdict math, and cookie parsers take data in and return data out.
47
+ - HTTP, disk session writes, and Typer I/O live in the shell.
48
+ - Fail closed (`ValueError` / typed `WallaError`) on invalid input.
49
+ - Prefer frozen pydantic models for domain transfers.
50
+
51
+ ---
52
+
53
+ ## 4. Credential Zero-Trust
54
+
55
+ - Never log a bearer access token or the NextAuth session cookie value.
56
+ - Session file (`~/.config/walla/session.json`) is mode `0600`.
57
+ - `doctor` reports `auth.session` as a boolean, never the secret.
58
+ - No `.env` credentials; login is cookie paste (optional password prompt).
59
+ - Redacted fixtures only. No tokens in `fixtures/`.
60
+
61
+ ---
62
+
63
+ ## 5. Machine Readability & Type Enforcement
64
+
65
+ - `mypy --strict` passes with 0 errors across `walla/`.
66
+ - Every library module defines `__all__`.
67
+ - No unicode em-dash (`\u2014`) or en-dash (`\u2013`) in code or messages.
68
+ - No bare `print()` in application code (CLI banner / catch helpers may write
69
+ to the Rich console; the quality script allowlists those files).
70
+
71
+ ---
72
+
73
+ ## 6. Verification Commands
74
+
75
+ ```bash
76
+ python scripts/check_code_quality.py
77
+ ruff check .
78
+ mypy walla scripts/check_code_quality.py
79
+ pytest -q -m "not live"
80
+ ```