walla-cli 0.1.7__tar.gz → 0.1.9__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 (109) hide show
  1. {walla_cli-0.1.7 → walla_cli-0.1.9}/AGENTS.md +3 -1
  2. {walla_cli-0.1.7 → walla_cli-0.1.9}/PKG-INFO +43 -35
  3. {walla_cli-0.1.7 → walla_cli-0.1.9}/README.md +41 -33
  4. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/WIRE.md +24 -0
  5. walla_cli-0.1.9/docs/assets/walla-demo-poster.jpg +0 -0
  6. walla_cli-0.1.9/docs/assets/walla-demo.mp4 +0 -0
  7. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/objective_function.md +1 -1
  8. walla_cli-0.1.9/fixtures/sell_item_request.json +51 -0
  9. walla_cli-0.1.9/fixtures/wire_contracts.json +230 -0
  10. {walla_cli-0.1.7 → walla_cli-0.1.9}/pyproject.toml +2 -2
  11. {walla_cli-0.1.7 → walla_cli-0.1.9}/scripts/check_code_quality.py +120 -1
  12. walla_cli-0.1.9/skills/walla/SKILL.md +34 -0
  13. walla_cli-0.1.9/skills/walla-buy/SKILL.md +35 -0
  14. walla_cli-0.1.9/skills/walla-sell/SKILL.md +41 -0
  15. walla_cli-0.1.9/tests/test_desk.py +196 -0
  16. walla_cli-0.1.9/tests/test_live.py +159 -0
  17. walla_cli-0.1.9/tests/test_sell.py +334 -0
  18. walla_cli-0.1.9/tests/test_wire_contracts.py +382 -0
  19. walla_cli-0.1.9/uv.lock +2644 -0
  20. walla_cli-0.1.9/walla/__init__.py +7 -0
  21. walla_cli-0.1.9/walla/account/desk.py +230 -0
  22. walla_cli-0.1.9/walla/account/sell.py +248 -0
  23. walla_cli-0.1.9/walla/account/sell_wire.py +144 -0
  24. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/banner.py +12 -8
  25. walla_cli-0.1.9/walla/cli/cmd_desk.py +37 -0
  26. walla_cli-0.1.9/walla/cli/cmd_sell.py +130 -0
  27. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/main.py +5 -4
  28. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/instruct.py +37 -21
  29. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/path.py +5 -0
  30. walla_cli-0.1.9/walla/hunter/converge.py +221 -0
  31. walla_cli-0.1.9/walla/hunter/pursuits.py +70 -0
  32. walla_cli-0.1.9/walla/hunter/stack.py +55 -0
  33. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/mcp/server.py +11 -0
  34. walla_cli-0.1.7/skills/walla/SKILL.md +0 -44
  35. walla_cli-0.1.7/tests/test_live.py +0 -68
  36. walla_cli-0.1.7/walla/__init__.py +0 -7
  37. {walla_cli-0.1.7 → walla_cli-0.1.9}/.cursor/rules/quality.mdc +0 -0
  38. {walla_cli-0.1.7 → walla_cli-0.1.9}/.github/workflows/ci.yml +0 -0
  39. {walla_cli-0.1.7 → walla_cli-0.1.9}/.gitignore +0 -0
  40. {walla_cli-0.1.7 → walla_cli-0.1.9}/LICENSE +0 -0
  41. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/assets/walla-github-banner.jpg +0 -0
  42. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/code_quality.md +0 -0
  43. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/data_engineering_standards.md +0 -0
  44. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/mcp.md +0 -0
  45. {walla_cli-0.1.7 → walla_cli-0.1.9}/docs/rate_limit_analysis.md +0 -0
  46. {walla_cli-0.1.7 → walla_cli-0.1.9}/fixtures/categories.json +0 -0
  47. {walla_cli-0.1.7 → walla_cli-0.1.9}/fixtures/item_detail.json +0 -0
  48. {walla_cli-0.1.7 → walla_cli-0.1.9}/fixtures/offer_buyer_request.json +0 -0
  49. {walla_cli-0.1.7 → walla_cli-0.1.9}/fixtures/search_bicicleta.json +0 -0
  50. {walla_cli-0.1.7 → walla_cli-0.1.9}/scripts/dogfood_run.py +0 -0
  51. {walla_cli-0.1.7 → walla_cli-0.1.9}/scripts/probe_rate.py +0 -0
  52. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_account.py +0 -0
  53. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_account_more.py +0 -0
  54. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_catch.py +0 -0
  55. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_chat_open.py +0 -0
  56. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_cli_json.py +0 -0
  57. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_core.py +0 -0
  58. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_edges.py +0 -0
  59. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_export.py +0 -0
  60. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_http_more.py +0 -0
  61. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_negotiate.py +0 -0
  62. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_parsers.py +0 -0
  63. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_polite.py +0 -0
  64. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_regressions.py +0 -0
  65. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_search_api.py +0 -0
  66. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_session_store.py +0 -0
  67. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_shortlist.py +0 -0
  68. {walla_cli-0.1.7 → walla_cli-0.1.9}/tests/test_verdict.py +0 -0
  69. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/__init__.py +0 -0
  70. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/actions.py +0 -0
  71. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/chat.py +0 -0
  72. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/cookies.py +0 -0
  73. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/inbox.py +0 -0
  74. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/login.py +0 -0
  75. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/offer_wire.py +0 -0
  76. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/prompt.py +0 -0
  77. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/account/session_store.py +0 -0
  78. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/__init__.py +0 -0
  79. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/catch.py +0 -0
  80. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/cmd_account.py +0 -0
  81. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/cmd_look.py +0 -0
  82. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/cmd_ops.py +0 -0
  83. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/cli/console.py +0 -0
  84. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/__init__.py +0 -0
  85. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/envelope.py +0 -0
  86. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/errors.py +0 -0
  87. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/exceptions.py +0 -0
  88. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/core/human_fix.py +0 -0
  89. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/http/__init__.py +0 -0
  90. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/http/client.py +0 -0
  91. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/http/headers.py +0 -0
  92. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/http/polite.py +0 -0
  93. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/__init__.py +0 -0
  94. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/export.py +0 -0
  95. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/export_pdf.py +0 -0
  96. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/negotiate.py +0 -0
  97. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/profile_store.py +0 -0
  98. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/shortlist.py +0 -0
  99. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/verdict.py +0 -0
  100. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/hunter/watches.py +0 -0
  101. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/mcp/__init__.py +0 -0
  102. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/mcp/_entry.py +0 -0
  103. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/models/__init__.py +0 -0
  104. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/models/account.py +0 -0
  105. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/models/listing.py +0 -0
  106. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/models/profile.py +0 -0
  107. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/search/__init__.py +0 -0
  108. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/search/api.py +0 -0
  109. {walla_cli-0.1.7 → walla_cli-0.1.9}/walla/search/parse.py +0 -0
@@ -57,9 +57,11 @@ Ranks: models 10 → exceptions 15 → http 20 → core 30 → search/account/hu
57
57
 
58
58
  - **Do** keep files <= 250 LOC.
59
59
  - **Do** fail closed; typed `error_type`.
60
- - **Do** require `--yes` for say/offer.
60
+ - **Do** require `--yes` for bare say/offer/sell/unsell. `pursue` and `desk` send negotiation chat without a per-message prompt. Never pay.
61
61
  - **Don't** commit session.json, cookies, or tokens.
62
62
  - **Don't** invent wire shapes without a fixture.
63
63
  - **Don't** post buyer offers as `{item_id, amount, currency}` (HTTP 400).
64
64
  Use `walla.account.offer_wire.build_offer_body` / `fixtures/offer_buyer_request.json`.
65
+ - **Don't** create listings without `Accept: application/vnd.upload-v2+json`
66
+ (HTTP 405). Use `walla sell` / `fixtures/sell_item_request.json`.
65
67
  - **Don't** require `.env` credentials; login is cookie paste.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: walla-cli
3
- Version: 0.1.7
4
- Summary: Wallapop.es CLI - look · offer · talk for humans and agents
3
+ Version: 0.1.9
4
+ Summary: Wallapop.es CLI - look · offer · talk · sell for humans and agents
5
5
  Project-URL: Homepage, https://github.com/berendgort/wallapop-cli
6
6
  Project-URL: Repository, https://github.com/berendgort/wallapop-cli
7
7
  Project-URL: Issues, https://github.com/berendgort/wallapop-cli/issues
@@ -51,43 +51,50 @@ Description-Content-Type: text/markdown
51
51
  ██║███╗██║ ██╔══██║ ██║ ██║ ██╔══██║ ╚════╝ ██║ ██║ ██║
52
52
  ╚███╔███╔╝ ██║ ██║ ███████╗ ███████╗ ██║ ██║ ╚██████╗ ███████╗ ██║
53
53
  ╚══╝╚══╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
54
- look · offer · talk
54
+ look · offer · talk · sell
55
55
  Dr. Berend Gort · www.berendgort.dev
56
56
  ```
57
57
 
58
- ![walla banner](docs/assets/walla-github-banner.jpg)
58
+ ![walla demo](docs/assets/walla-demo.mp4)
59
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.
60
+ Demo music: [Funkee Monkeee](https://mixkit.co/free-stock-music/funkee-monkeee-1140/) by Michael Ramir C.
61
+ ([Mixkit Stock Music Free License](https://mixkit.co/license/#musicFree)).
62
62
 
63
- Not affiliated with Wallapop. Respect ToS and rate limits.
63
+ **walla** turns Claude, GPT, or Cursor into a Wallapop helper that actually
64
+ *does* the work for you. You drop a few photos and answer a couple of
65
+ questions; the agent publishes the listing. When buyers message, it reads
66
+ the chats, replies for you, and negotiates down to your minimum price —
67
+ then shows you the best deal. You only open the Wallapop app to accept.
68
+ No typing chats. No babysitting the inbox.
69
+
70
+ Unofficial Wallapop.es CLI + MCP + Python library. Not affiliated with
71
+ Wallapop. Respect ToS and rate limits.
64
72
 
65
73
  ### What this unlocks
66
74
 
67
75
  You tell Cursor: *“Find me a used kite near Barcelona under €400, message
68
76
  the seller, and offer if it’s a GRAB.”*
69
77
 
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:
78
+ The agent runs `walla` — ranks listings **GRAB / LOOK / PASS**, messages the
79
+ shortlist, and follows replies until a price is agreed. You close the deal
80
+ in the Wallapop app.
72
81
 
73
82
  ```bash
74
83
  walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona \
75
84
  --budget 400 --aggression fair --must "kite cabrinha"
76
- walla search "tabla kite Cabrinha" --max 40 --json
77
- # agent: GRAB · Cabrinha Moto 10m · €320 · 4 km
78
- # https://es.wallapop.com/item/...
79
-
80
- walla search "…" --export md,csv,html,pdf --out ./shortlist --json
81
- # or later: walla export --format md,csv,html,pdf --out ./shortlist --json
82
-
83
85
  walla login # paste session cookie once
84
- walla negotiate <id> --json # draft only; you approve text + EUR
85
- walla say <id> "…" --yes # only after you approve
86
- walla offer <id> --eur 280 --yes # only after you approve; never pays
86
+ walla pursue "tabla kite Cabrinha" --json
87
+ walla desk --json # one inbox read; replies; stack.best
87
88
  ```
88
89
 
89
- That is the product: a Cursor agent that can **look, draft, talk, and offer**
90
- up to the point of payment. You finish pay/meet in the Wallapop app.
90
+ Sell is the same idea: answer the photo questions, publish once, then desk
91
+ talks to buyers.
92
+
93
+ ```bash
94
+ walla sell ./shot.jpg --json
95
+ walla sell ./shot.jpg --title "…" --desc "…" --eur 40 \
96
+ --category 10105 --root 12579 --floor 35 --yes --json
97
+ ```
91
98
 
92
99
  **Quality bar (enforced in CI):**
93
100
 
@@ -139,26 +146,25 @@ Optional MCP:
139
146
  | Ask | Command |
140
147
  |-----|---------|
141
148
  | Mandate | `walla setup --budget 400 --aggression fair --must "kite"` |
142
- | Look | `walla search "tabla kite" --max 40 --json` then `walla item <id> --json` |
143
- | Draft | `walla negotiate <id> --json` (win-win Spanish draft; never sends) |
144
- | Talk | `walla inbox --json` · `walla thread <id> --json` · `walla say <id> "…" --yes` |
145
- | Offer | `walla offer <id> --eur 180 --yes` |
149
+ | Look | `walla search "tabla kite" --max 40 --json` |
150
+ | Buy | `walla pursue "<query>" --json` then `walla desk --json` |
151
+ | Sell | `walla sell <photos> --json`, then the same command with `--yes` |
152
+ | Close | Human pays or accepts in the Wallapop app |
146
153
 
147
- ### HITL (ask once per decision)
154
+ ### HITL
148
155
 
149
156
  | Step | Agent alone | Ask human |
150
157
  |------|-------------|-----------|
151
158
  | Search inside mandate | yes | if query vague or budget unset |
152
- | Shortlist | show ranked Wallapop rows with markdown item links | if more than one GRAB, pick the id |
153
- | Negotiate draft | yes | approve Spanish text |
154
- | `say --yes` | never | yes to that exact text |
155
- | `offer --yes` | never; refuse if EUR > budget | yes to that exact EUR |
156
- | Pay / buy / reserve | **never** | human finishes in the Wallapop app |
159
+ | `pursue` / `desk` | yes, including the chat messages | never for each text |
160
+ | Publish or delete a listing | no | `--yes` once the questions are answered |
161
+ | Pay / accept / meet | **never** | human finishes in the Wallapop app |
157
162
 
158
163
  Rules:
159
164
 
160
- - Reads and negotiate drafts are safe. `say` / `offer` need `--yes`.
161
- - `--yes` means send chat or price offer only. It never completes a purchase.
165
+ - `pursue` and `desk` send negotiation chat. Do not ask the human to send.
166
+ - Bare `say` / `offer` / `sell` / `unsell` still need `--yes`.
167
+ - `--yes` never completes a purchase or accepts a sale.
162
168
  - If they only asked to look, do not send.
163
169
  - Ambiguous match or multiple GRABs: ask once with candidates.
164
170
  - Always present Wallapop shortlists ranked best-first with a markdown link on every item URL.
@@ -187,9 +193,11 @@ walla doctor --json
187
193
  | `walla search` / `item` / `categories` | Look |
188
194
  | `walla watch add\|list\|check` | Local saved searches |
189
195
  | `walla login` / `logout` / `whoami` | Session (paste cookie; optional `--password`) |
196
+ | `walla pursue` / `desk` | Message a shortlist and follow the inbox |
190
197
  | `walla negotiate` | Win-win Spanish draft (never sends) |
191
- | `walla inbox` / `thread` / `say` | Talk |
192
- | `walla offer` | Offer (prints total, needs `--yes`) |
198
+ | `walla inbox` / `thread` / `say` | Talk (`say` needs `--yes`) |
199
+ | `walla offer` | Formal offer (needs `--yes`) |
200
+ | `walla sell` / `unsell` | Publish or delete a listing (`--yes`) |
193
201
  | `walla fav list\|add\|rm` | Favorites |
194
202
  | `walla instruct` / `doctor` | Agent recipe + health |
195
203
 
@@ -7,43 +7,50 @@
7
7
  ██║███╗██║ ██╔══██║ ██║ ██║ ██╔══██║ ╚════╝ ██║ ██║ ██║
8
8
  ╚███╔███╔╝ ██║ ██║ ███████╗ ███████╗ ██║ ██║ ╚██████╗ ███████╗ ██║
9
9
  ╚══╝╚══╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
10
- look · offer · talk
10
+ look · offer · talk · sell
11
11
  Dr. Berend Gort · www.berendgort.dev
12
12
  ```
13
13
 
14
- ![walla banner](docs/assets/walla-github-banner.jpg)
14
+ ![walla demo](docs/assets/walla-demo.mp4)
15
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.
16
+ Demo music: [Funkee Monkeee](https://mixkit.co/free-stock-music/funkee-monkeee-1140/) by Michael Ramir C.
17
+ ([Mixkit Stock Music Free License](https://mixkit.co/license/#musicFree)).
18
18
 
19
- Not affiliated with Wallapop. Respect ToS and rate limits.
19
+ **walla** turns Claude, GPT, or Cursor into a Wallapop helper that actually
20
+ *does* the work for you. You drop a few photos and answer a couple of
21
+ questions; the agent publishes the listing. When buyers message, it reads
22
+ the chats, replies for you, and negotiates down to your minimum price —
23
+ then shows you the best deal. You only open the Wallapop app to accept.
24
+ No typing chats. No babysitting the inbox.
25
+
26
+ Unofficial Wallapop.es CLI + MCP + Python library. Not affiliated with
27
+ Wallapop. Respect ToS and rate limits.
20
28
 
21
29
  ### What this unlocks
22
30
 
23
31
  You tell Cursor: *“Find me a used kite near Barcelona under €400, message
24
32
  the seller, and offer if it’s a GRAB.”*
25
33
 
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:
34
+ The agent runs `walla` — ranks listings **GRAB / LOOK / PASS**, messages the
35
+ shortlist, and follows replies until a price is agreed. You close the deal
36
+ in the Wallapop app.
28
37
 
29
38
  ```bash
30
39
  walla setup --lat 41.39 --lon 2.17 --km 30 --label Barcelona \
31
40
  --budget 400 --aggression fair --must "kite cabrinha"
32
- walla search "tabla kite Cabrinha" --max 40 --json
33
- # agent: GRAB · Cabrinha Moto 10m · €320 · 4 km
34
- # https://es.wallapop.com/item/...
35
-
36
- walla search "…" --export md,csv,html,pdf --out ./shortlist --json
37
- # or later: walla export --format md,csv,html,pdf --out ./shortlist --json
38
-
39
41
  walla login # paste session cookie once
40
- walla negotiate <id> --json # draft only; you approve text + EUR
41
- walla say <id> "…" --yes # only after you approve
42
- walla offer <id> --eur 280 --yes # only after you approve; never pays
42
+ walla pursue "tabla kite Cabrinha" --json
43
+ walla desk --json # one inbox read; replies; stack.best
43
44
  ```
44
45
 
45
- That is the product: a Cursor agent that can **look, draft, talk, and offer**
46
- up to the point of payment. You finish pay/meet in the Wallapop app.
46
+ Sell is the same idea: answer the photo questions, publish once, then desk
47
+ talks to buyers.
48
+
49
+ ```bash
50
+ walla sell ./shot.jpg --json
51
+ walla sell ./shot.jpg --title "…" --desc "…" --eur 40 \
52
+ --category 10105 --root 12579 --floor 35 --yes --json
53
+ ```
47
54
 
48
55
  **Quality bar (enforced in CI):**
49
56
 
@@ -95,26 +102,25 @@ Optional MCP:
95
102
  | Ask | Command |
96
103
  |-----|---------|
97
104
  | Mandate | `walla setup --budget 400 --aggression fair --must "kite"` |
98
- | Look | `walla search "tabla kite" --max 40 --json` then `walla item <id> --json` |
99
- | Draft | `walla negotiate <id> --json` (win-win Spanish draft; never sends) |
100
- | Talk | `walla inbox --json` · `walla thread <id> --json` · `walla say <id> "…" --yes` |
101
- | Offer | `walla offer <id> --eur 180 --yes` |
105
+ | Look | `walla search "tabla kite" --max 40 --json` |
106
+ | Buy | `walla pursue "<query>" --json` then `walla desk --json` |
107
+ | Sell | `walla sell <photos> --json`, then the same command with `--yes` |
108
+ | Close | Human pays or accepts in the Wallapop app |
102
109
 
103
- ### HITL (ask once per decision)
110
+ ### HITL
104
111
 
105
112
  | Step | Agent alone | Ask human |
106
113
  |------|-------------|-----------|
107
114
  | Search inside mandate | yes | if query vague or budget unset |
108
- | Shortlist | show ranked Wallapop rows with markdown item links | if more than one GRAB, pick the id |
109
- | Negotiate draft | yes | approve Spanish text |
110
- | `say --yes` | never | yes to that exact text |
111
- | `offer --yes` | never; refuse if EUR > budget | yes to that exact EUR |
112
- | Pay / buy / reserve | **never** | human finishes in the Wallapop app |
115
+ | `pursue` / `desk` | yes, including the chat messages | never for each text |
116
+ | Publish or delete a listing | no | `--yes` once the questions are answered |
117
+ | Pay / accept / meet | **never** | human finishes in the Wallapop app |
113
118
 
114
119
  Rules:
115
120
 
116
- - Reads and negotiate drafts are safe. `say` / `offer` need `--yes`.
117
- - `--yes` means send chat or price offer only. It never completes a purchase.
121
+ - `pursue` and `desk` send negotiation chat. Do not ask the human to send.
122
+ - Bare `say` / `offer` / `sell` / `unsell` still need `--yes`.
123
+ - `--yes` never completes a purchase or accepts a sale.
118
124
  - If they only asked to look, do not send.
119
125
  - Ambiguous match or multiple GRABs: ask once with candidates.
120
126
  - Always present Wallapop shortlists ranked best-first with a markdown link on every item URL.
@@ -143,9 +149,11 @@ walla doctor --json
143
149
  | `walla search` / `item` / `categories` | Look |
144
150
  | `walla watch add\|list\|check` | Local saved searches |
145
151
  | `walla login` / `logout` / `whoami` | Session (paste cookie; optional `--password`) |
152
+ | `walla pursue` / `desk` | Message a shortlist and follow the inbox |
146
153
  | `walla negotiate` | Win-win Spanish draft (never sends) |
147
- | `walla inbox` / `thread` / `say` | Talk |
148
- | `walla offer` | Offer (prints total, needs `--yes`) |
154
+ | `walla inbox` / `thread` / `say` | Talk (`say` needs `--yes`) |
155
+ | `walla offer` | Formal offer (needs `--yes`) |
156
+ | `walla sell` / `unsell` | Publish or delete a listing (`--yes`) |
149
157
  | `walla fav list\|add\|rm` | Favorites |
150
158
  | `walla instruct` / `doctor` | Agent recipe + health |
151
159
 
@@ -57,6 +57,30 @@ login ever succeeds.
57
57
  Some sellers disable offers → **409** `offer creation not allowed`; fall back to chat text.
58
58
  - Favorites: `/api/v3/users/me/favorites`, `/api/v3/items/{id}/favorite`
59
59
 
60
+ ## Sell / Vender (auth)
61
+
62
+ Browser: `https://es.wallapop.com/app/catalog/upload/consumer-goods`.
63
+
64
+ 1. `POST /api/v3/steps` `{mode:{action:"upload",id:<uuid>}}` → step `title`
65
+ 2. `POST /api/v3/steps` `current_step=title` draft `{title}` → `photo`
66
+ 3. `POST /api/v3/upload/{upload_id}/pictures` multipart `file` → **204**
67
+ 4. `POST /api/v3/steps` `current_step=photo` → `category`
68
+ 5. `POST /api/v3/steps` `current_step=category` draft
69
+ `{title, category_leaf_id, root_category_id}` (**ids as strings**) → `loading`
70
+ 6. Poll `GET /api/v3/suggested-item-data/{upload_id}` (404 then empty 200)
71
+ 7. `POST /api/v3/steps` `current_step=loading` → `listing`
72
+ 8. Create: `POST /api/v3/items` multipart `image` + `item` (JSON string).
73
+ Header **`Accept: application/vnd.upload-v2+json`** (without it → HTTP 405).
74
+ Extra photos: `POST /api/v3/items/{id}/picture2` multipart `image` + `order`.
75
+ 9. Delete: `DELETE /api/v3/items/{id}` → 204.
76
+
77
+ Fixture: `fixtures/sell_item_request.json` / `walla.account.sell_wire.build_item_body`.
78
+
79
+ Every CLI call (method, path, auth, body) is listed in `fixtures/wire_contracts.json`.
80
+ `scripts/check_code_quality.py` fails if `walla/account`, `walla/search`, or `walla/http`
81
+ grows a `/api/` or `/bff/` literal that the catalog does not list.
82
+ CLI: `walla sell photo.jpg … --yes` (draft without `--yes`).
83
+
60
84
  Writes that fail closed as `error_type: unsupported` until fixtures exist.
61
85
 
62
86
  ## Rate limits
@@ -2,6 +2,6 @@
2
2
 
3
3
  1. Find the listing (search + verdict GRAB / LOOK / PASS).
4
4
  2. Do not miss a reply (inbox / thread).
5
- 3. Never send an offer or chat message by accident (`--yes` / `confirm=true`).
5
+ 3. `walla say` / `walla offer` still need `--yes`. `walla pursue` and `walla desk` send negotiation chat on purpose. Never pay.
6
6
  4. Never leak session cookies or bearer tokens.
7
7
  5. Stay polite on rate limits.
@@ -0,0 +1,51 @@
1
+ {
2
+ "endpoint": "POST /api/v3/items",
3
+ "content_type": "multipart/form-data",
4
+ "accept": "application/vnd.upload-v2+json",
5
+ "fields": {
6
+ "image": "first JPEG/PNG/WebP file",
7
+ "item": "JSON string (see example)"
8
+ },
9
+ "steps": [
10
+ "POST /api/v3/steps mode.upload",
11
+ "POST /api/v3/steps current_step=title draft.title",
12
+ "POST /api/v3/upload/{upload_id}/pictures multipart file",
13
+ "POST /api/v3/steps current_step=photo",
14
+ "POST /api/v3/steps current_step=category draft.category_leaf_id+root_category_id (strings)",
15
+ "GET /api/v3/suggested-item-data/{upload_id} until 200",
16
+ "POST /api/v3/steps current_step=loading",
17
+ "POST /api/v3/items Accept upload-v2"
18
+ ],
19
+ "delete": "DELETE /api/v3/items/{id}",
20
+ "example": {
21
+ "attributes": {
22
+ "title": "TEST walla CLI - borrar",
23
+ "description": "Anuncio de prueba. NO COMPRAR.",
24
+ "price_amount": 9,
25
+ "condition": "good"
26
+ },
27
+ "category_leaf_id": "10105",
28
+ "apply_discount": false,
29
+ "location": {
30
+ "latitude": 41.3874,
31
+ "longitude": 2.1686,
32
+ "approximated": true
33
+ },
34
+ "delivery": {
35
+ "allowed_by_user": false,
36
+ "max_weight_kg": null,
37
+ "cost_configuration_id": null
38
+ },
39
+ "upload_id": "00000000-0000-0000-0000-000000000000"
40
+ },
41
+ "required_keys": [
42
+ "attributes",
43
+ "category_leaf_id",
44
+ "apply_discount",
45
+ "location",
46
+ "delivery",
47
+ "upload_id"
48
+ ],
49
+ "attribute_required_keys": ["title", "description", "price_amount"],
50
+ "conditions": ["as_good_as_new", "good", "fair", "has_given_it_all"]
51
+ }
@@ -0,0 +1,230 @@
1
+ {
2
+ "shared_headers": ["X-DeviceOS", "X-AppVersion", "Accept-Language"],
3
+ "requests": [
4
+ {
5
+ "id": "search",
6
+ "cli": "search",
7
+ "method": "GET",
8
+ "path": "/api/v3/search",
9
+ "auth": false,
10
+ "ok_status": [200]
11
+ },
12
+ {
13
+ "id": "item",
14
+ "cli": "item",
15
+ "method": "GET",
16
+ "path": "/api/v3/items/{id}",
17
+ "auth": false,
18
+ "ok_status": [200]
19
+ },
20
+ {
21
+ "id": "categories",
22
+ "cli": "categories",
23
+ "method": "GET",
24
+ "path": "/api/v3/categories",
25
+ "auth": false,
26
+ "ok_status": [200]
27
+ },
28
+ {
29
+ "id": "session_mint",
30
+ "cli": "login",
31
+ "method": "GET",
32
+ "path": "/api/auth/session",
33
+ "auth": false,
34
+ "ok_status": [200]
35
+ },
36
+ {
37
+ "id": "password_login",
38
+ "cli": "login",
39
+ "method": "POST",
40
+ "path": "/api/v3/access/login",
41
+ "auth": false,
42
+ "body_keys": ["username", "password"],
43
+ "ok_status": [200]
44
+ },
45
+ {
46
+ "id": "refresh",
47
+ "cli": "login",
48
+ "method": "POST",
49
+ "path": "/api/v3/access/refresh",
50
+ "auth": false,
51
+ "body_keys": ["refresh_token"],
52
+ "ok_status": [200]
53
+ },
54
+ {
55
+ "id": "whoami",
56
+ "cli": "whoami",
57
+ "method": "GET",
58
+ "path": "/api/v3/users/me",
59
+ "auth": true,
60
+ "ok_status": [200, 404]
61
+ },
62
+ {
63
+ "id": "inbox",
64
+ "cli": "inbox",
65
+ "method": "GET",
66
+ "path": "/bff/messaging/inbox",
67
+ "auth": true,
68
+ "ok_status": [200],
69
+ "known_bad": [{"status": 405, "cause": "GET /api/v3/conversations"}]
70
+ },
71
+ {
72
+ "id": "chat_open",
73
+ "cli": "chat",
74
+ "method": "POST",
75
+ "path": "/api/v3/conversations",
76
+ "auth": true,
77
+ "body_keys": ["item_id"],
78
+ "ok_status": [200, 201]
79
+ },
80
+ {
81
+ "id": "im_token",
82
+ "cli": "say",
83
+ "method": "GET",
84
+ "path": "/api/v3/instant-messaging/token",
85
+ "auth": true,
86
+ "ok_status": [200]
87
+ },
88
+ {
89
+ "id": "say_pubnub",
90
+ "cli": "say",
91
+ "method": "GET",
92
+ "path": "https://ps2.pndsn.com/publish/",
93
+ "needle": "ps2.pndsn.com",
94
+ "auth": false,
95
+ "ok_status": [200],
96
+ "known_bad": [{"status": 404, "cause": "REST messages"}]
97
+ },
98
+ {
99
+ "id": "offer",
100
+ "cli": "offer",
101
+ "method": "POST",
102
+ "path": "/api/v3/delivery/buyer/offers",
103
+ "auth": true,
104
+ "body_keys": [
105
+ "offer_id",
106
+ "offer_price_amount",
107
+ "offer_price_currency",
108
+ "item_ids"
109
+ ],
110
+ "forbidden_body_keys": ["item_id", "amount", "currency"],
111
+ "ok_status": [200, 201],
112
+ "known_bad": [
113
+ {"status": 400, "cause": "wrong body or missing X-AppVersion"},
114
+ {"status": 409, "cause": "seller disabled offers"}
115
+ ]
116
+ },
117
+ {
118
+ "id": "favorites",
119
+ "cli": "fav list",
120
+ "method": "GET",
121
+ "path": "/api/v3/users/me/favorites",
122
+ "auth": true,
123
+ "ok_status": [200]
124
+ },
125
+ {
126
+ "id": "favorites_fallback",
127
+ "cli": "fav list",
128
+ "method": "GET",
129
+ "path": "/api/v3/favorites",
130
+ "auth": true,
131
+ "ok_status": [200]
132
+ },
133
+ {
134
+ "id": "favorite_add",
135
+ "cli": "fav add",
136
+ "method": "POST",
137
+ "path": "/api/v3/items/{id}/favorite",
138
+ "needle": "/favorite",
139
+ "auth": true,
140
+ "ok_status": [200, 201, 204]
141
+ },
142
+ {
143
+ "id": "favorite_rm",
144
+ "cli": "fav rm",
145
+ "method": "DELETE",
146
+ "path": "/api/v3/items/{id}/favorite",
147
+ "needle": "/favorite",
148
+ "auth": true,
149
+ "ok_status": [200, 204]
150
+ },
151
+ {
152
+ "id": "sell_steps",
153
+ "cli": "sell",
154
+ "method": "POST",
155
+ "path": "/api/v3/steps",
156
+ "auth": true,
157
+ "ok_status": [200]
158
+ },
159
+ {
160
+ "id": "sell_pictures",
161
+ "cli": "sell",
162
+ "method": "POST",
163
+ "path": "/api/v3/upload/{id}/pictures",
164
+ "needle": "/api/v3/upload/",
165
+ "auth": true,
166
+ "multipart": ["file"],
167
+ "ok_status": [200, 204]
168
+ },
169
+ {
170
+ "id": "sell_suggest",
171
+ "cli": "sell",
172
+ "method": "GET",
173
+ "path": "/api/v3/suggested-item-data/{id}",
174
+ "needle": "/suggested-item-data/",
175
+ "auth": true,
176
+ "ok_status": [200, 404]
177
+ },
178
+ {
179
+ "id": "sell_create",
180
+ "cli": "sell",
181
+ "method": "POST",
182
+ "path": "/api/v3/items",
183
+ "needle": "/api/v3/items",
184
+ "auth": true,
185
+ "accept": "application/vnd.upload-v2+json",
186
+ "multipart": ["image", "item"],
187
+ "ok_status": [200, 201],
188
+ "known_bad": [{"status": 405, "cause": "missing upload-v2 Accept"}]
189
+ },
190
+ {
191
+ "id": "sell_picture2",
192
+ "cli": "sell",
193
+ "method": "POST",
194
+ "path": "/api/v3/items/{id}/picture2",
195
+ "needle": "/picture2",
196
+ "auth": true,
197
+ "multipart": ["image", "order"],
198
+ "ok_status": [200, 201, 204]
199
+ },
200
+ {
201
+ "id": "unsell",
202
+ "cli": "unsell",
203
+ "method": "DELETE",
204
+ "path": "/api/v3/items/{id}",
205
+ "needle": "/api/v3/items/",
206
+ "auth": true,
207
+ "ok_status": [200, 204]
208
+ },
209
+ {
210
+ "id": "inbox_not_conversations",
211
+ "cli": "inbox",
212
+ "method": "GET",
213
+ "path": "/api/v3/conversations",
214
+ "forbidden": true,
215
+ "auth": true,
216
+ "ok_status": [],
217
+ "known_bad": [{"status": 405, "cause": "inbox is /bff/messaging/inbox"}]
218
+ },
219
+ {
220
+ "id": "say_not_rest_messages",
221
+ "cli": "say",
222
+ "method": "POST",
223
+ "path": "/api/v3/instant-messaging/messages",
224
+ "forbidden": true,
225
+ "auth": true,
226
+ "ok_status": [],
227
+ "known_bad": [{"status": 404, "cause": "say uses PubNub publish"}]
228
+ }
229
+ ]
230
+ }
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "walla-cli"
3
- version = "0.1.7"
4
- description = "Wallapop.es CLI - look · offer · talk for humans and agents"
3
+ version = "0.1.9"
4
+ description = "Wallapop.es CLI - look · offer · talk · sell for humans and agents"
5
5
  authors = [{ name = "Dr. Berend Gort", email = "berend.gort@gmail.com" }]
6
6
  readme = "README.md"
7
7
  license = { text = "MIT" }