walla-cli 0.1.5__tar.gz → 0.1.6__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 (83) hide show
  1. {walla_cli-0.1.5 → walla_cli-0.1.6}/PKG-INFO +1 -1
  2. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/WIRE.md +3 -1
  3. {walla_cli-0.1.5 → walla_cli-0.1.6}/pyproject.toml +1 -1
  4. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_account.py +5 -0
  5. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_negotiate.py +10 -0
  6. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/__init__.py +1 -1
  7. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/actions.py +7 -3
  8. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/negotiate.py +5 -11
  9. {walla_cli-0.1.5 → walla_cli-0.1.6}/.cursor/rules/quality.mdc +0 -0
  10. {walla_cli-0.1.5 → walla_cli-0.1.6}/.github/workflows/ci.yml +0 -0
  11. {walla_cli-0.1.5 → walla_cli-0.1.6}/.gitignore +0 -0
  12. {walla_cli-0.1.5 → walla_cli-0.1.6}/AGENTS.md +0 -0
  13. {walla_cli-0.1.5 → walla_cli-0.1.6}/LICENSE +0 -0
  14. {walla_cli-0.1.5 → walla_cli-0.1.6}/README.md +0 -0
  15. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/assets/walla-github-banner.jpg +0 -0
  16. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/code_quality.md +0 -0
  17. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/data_engineering_standards.md +0 -0
  18. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/mcp.md +0 -0
  19. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/objective_function.md +0 -0
  20. {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/rate_limit_analysis.md +0 -0
  21. {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/categories.json +0 -0
  22. {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/item_detail.json +0 -0
  23. {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/search_bicicleta.json +0 -0
  24. {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/check_code_quality.py +0 -0
  25. {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/dogfood_run.py +0 -0
  26. {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/probe_rate.py +0 -0
  27. {walla_cli-0.1.5 → walla_cli-0.1.6}/skills/walla/SKILL.md +0 -0
  28. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_account_more.py +0 -0
  29. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_catch.py +0 -0
  30. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_cli_json.py +0 -0
  31. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_core.py +0 -0
  32. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_edges.py +0 -0
  33. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_export.py +0 -0
  34. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_http_more.py +0 -0
  35. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_live.py +0 -0
  36. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_parsers.py +0 -0
  37. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_polite.py +0 -0
  38. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_search_api.py +0 -0
  39. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_session_store.py +0 -0
  40. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_shortlist.py +0 -0
  41. {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_verdict.py +0 -0
  42. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/__init__.py +0 -0
  43. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/cookies.py +0 -0
  44. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/inbox.py +0 -0
  45. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/login.py +0 -0
  46. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/prompt.py +0 -0
  47. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/session_store.py +0 -0
  48. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/__init__.py +0 -0
  49. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/banner.py +0 -0
  50. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/catch.py +0 -0
  51. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_account.py +0 -0
  52. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_look.py +0 -0
  53. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_ops.py +0 -0
  54. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/console.py +0 -0
  55. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/main.py +0 -0
  56. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/__init__.py +0 -0
  57. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/envelope.py +0 -0
  58. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/errors.py +0 -0
  59. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/exceptions.py +0 -0
  60. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/human_fix.py +0 -0
  61. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/instruct.py +0 -0
  62. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/path.py +0 -0
  63. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/__init__.py +0 -0
  64. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/client.py +0 -0
  65. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/headers.py +0 -0
  66. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/polite.py +0 -0
  67. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/__init__.py +0 -0
  68. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/export.py +0 -0
  69. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/export_pdf.py +0 -0
  70. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/profile_store.py +0 -0
  71. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/shortlist.py +0 -0
  72. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/verdict.py +0 -0
  73. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/watches.py +0 -0
  74. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/__init__.py +0 -0
  75. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/_entry.py +0 -0
  76. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/server.py +0 -0
  77. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/__init__.py +0 -0
  78. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/account.py +0 -0
  79. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/listing.py +0 -0
  80. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/profile.py +0 -0
  81. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/search/__init__.py +0 -0
  82. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/search/api.py +0 -0
  83. {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/search/parse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: walla-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Wallapop.es CLI - look · offer · talk 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
@@ -48,7 +48,9 @@ Reported shapes (confirm with a live token before trusting writes):
48
48
 
49
49
  - Inbox: `GET /api/v3/conversations`
50
50
  - Messages: `GET|POST /api/v3/conversations/{id}/messages`
51
- - Offers: `POST /api/v3/delivery/buyer/offers`
51
+ - Offers: `POST /api/v3/delivery/buyer/offers` with
52
+ `{offer_id, offer_price_amount, offer_price_currency, item_ids}`.
53
+ `offer_id` is a client UUID. `item_ids` is a one-element list of the listing id.
52
54
  - Favorites: `/api/v3/users/me/favorites`, `/api/v3/items/{id}/favorite`
53
55
 
54
56
  Writes that fail closed as `error_type: unsupported` until fixtures exist.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "walla-cli"
3
- version = "0.1.5"
3
+ version = "0.1.6"
4
4
  description = "Wallapop.es CLI - look · offer · talk for humans and agents"
5
5
  authors = [{ name = "Dr. Berend Gort", email = "berend.gort@gmail.com" }]
6
6
  readme = "README.md"
@@ -126,3 +126,8 @@ def test_make_offer_confirm_and_wire(
126
126
  out = make_offer("1", 40, confirm=True)
127
127
  assert out["sent"] is True
128
128
  assert out["quote"]["offer_eur"] == 40
129
+ body = client.post.call_args.kwargs["json_body"]
130
+ assert body["item_ids"] == ["1"]
131
+ assert body["offer_price_amount"] == 40
132
+ assert body["offer_price_currency"] == "EUR"
133
+ assert body["offer_id"] == out["offer_id"]
@@ -51,10 +51,20 @@ def test_draft_opening_is_specific_and_polite() -> None:
51
51
  def test_offer_band_never_lowballs() -> None:
52
52
  profile = Profile(lat=41.39, lon=2.17, km=30, pickup_km=30, budget=400)
53
53
  brief = draft_negotiation(_listing(price=200), profile)
54
+ assert brief.offer_eur == 164 # fair is 82% of ask
54
55
  assert brief.offer_eur >= 200 * 0.80
55
56
  assert brief.offer_eur <= brief.walk_away_eur
56
57
 
57
58
 
59
+ def test_fair_on_800_opens_near_650() -> None:
60
+ profile = Profile(lat=41.39, lon=2.17, km=30, pickup_km=30, budget=850)
61
+ brief = draft_negotiation(
62
+ _listing(price=800, ship=True, title="North Reach 13m"),
63
+ profile,
64
+ )
65
+ assert brief.offer_eur == 656
66
+
67
+
58
68
  def test_firm_offer_at_floor_within_budget() -> None:
59
69
  profile = Profile(
60
70
  lat=41.39, lon=2.17, km=30, pickup_km=30, budget=400, aggressiveness="firm"
@@ -4,4 +4,4 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = ("__version__",)
6
6
 
7
- __version__ = "0.1.5"
7
+ __version__ = "0.1.6"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import uuid
5
6
  from typing import Any
6
7
 
7
8
  from walla.account.login import ensure_access_token
@@ -116,18 +117,21 @@ def make_offer(
116
117
  f"In-person listing outside pickup radius ({item.title}). Refuse offer."
117
118
  )
118
119
  http = client or _auth_client()
120
+ offer_id = str(uuid.uuid4())
119
121
  try:
120
122
  raw = http.post(
121
123
  "/api/v3/delivery/buyer/offers",
122
124
  json_body={
123
- "item_id": item_id,
124
- "amount": offer_eur,
125
- "currency": "EUR",
125
+ "offer_id": offer_id,
126
+ "offer_price_amount": offer_eur,
127
+ "offer_price_currency": "EUR",
128
+ "item_ids": [item_id],
126
129
  },
127
130
  auth=True,
128
131
  )
129
132
  return {
130
133
  "sent": True,
134
+ "offer_id": offer_id,
131
135
  "quote": quote.model_dump(mode="json"),
132
136
  "title": item.title,
133
137
  "response": raw,
@@ -31,8 +31,8 @@ BANNED_PHRASES = (
31
31
  )
32
32
 
33
33
  _AGGRESSION_PCT: dict[Aggression, float] = {
34
- "soft": 0.95,
35
- "fair": 0.90,
34
+ "soft": 0.90,
35
+ "fair": 0.82,
36
36
  "firm": 0.80,
37
37
  }
38
38
 
@@ -82,7 +82,7 @@ def draft_negotiation(
82
82
  f"Listing is PASS under mandate (must_match / budget / radius): {listing.title}"
83
83
  )
84
84
  walk = _walk_away(ask, profile)
85
- offer = _first_offer(ask, walk, verdict, profile.aggressiveness)
85
+ offer = _first_offer(ask, walk, profile.aggressiveness)
86
86
  specific = _specific_thing(listing)
87
87
  greeting = f"Hola {seller_name}," if seller_name else "Hola,"
88
88
  opening, offer_line, you_give = _copy(greeting, specific, offer, _ships(listing))
@@ -159,17 +159,11 @@ def _walk_away(ask: float, profile: Profile) -> float:
159
159
  return round(min(cap, ask), 2)
160
160
 
161
161
 
162
- def _first_offer(
163
- ask: float, walk: float, verdict: str, aggressiveness: Aggression
164
- ) -> float:
162
+ def _first_offer(ask: float, walk: float, aggressiveness: Aggression) -> float:
165
163
  """Aggression sets the ask percent; never below 80% on first message."""
166
164
  if ask <= 0:
167
165
  return 0.0
168
- pct = _AGGRESSION_PCT.get(aggressiveness, 0.90)
169
- if ask <= walk and verdict == "GRAB" and aggressiveness == "fair":
170
- pct = max(pct, 0.95)
171
- elif ask <= walk and verdict == "GRAB" and aggressiveness == "soft":
172
- pct = 0.98
166
+ pct = _AGGRESSION_PCT.get(aggressiveness, 0.82)
173
167
  raw = ask * pct
174
168
  floor = ask * 0.80
175
169
  offer = max(floor, min(raw, walk, ask))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes