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.
- {walla_cli-0.1.5 → walla_cli-0.1.6}/PKG-INFO +1 -1
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/WIRE.md +3 -1
- {walla_cli-0.1.5 → walla_cli-0.1.6}/pyproject.toml +1 -1
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_account.py +5 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_negotiate.py +10 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/__init__.py +1 -1
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/actions.py +7 -3
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/negotiate.py +5 -11
- {walla_cli-0.1.5 → walla_cli-0.1.6}/.cursor/rules/quality.mdc +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/.github/workflows/ci.yml +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/.gitignore +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/AGENTS.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/LICENSE +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/README.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/assets/walla-github-banner.jpg +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/code_quality.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/data_engineering_standards.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/mcp.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/objective_function.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/docs/rate_limit_analysis.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/categories.json +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/item_detail.json +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/fixtures/search_bicicleta.json +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/check_code_quality.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/dogfood_run.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/scripts/probe_rate.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/skills/walla/SKILL.md +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_account_more.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_catch.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_cli_json.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_core.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_edges.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_export.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_http_more.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_live.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_parsers.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_polite.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_search_api.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_session_store.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_shortlist.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/tests/test_verdict.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/cookies.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/inbox.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/login.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/prompt.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/account/session_store.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/banner.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/catch.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_account.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_look.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/cmd_ops.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/console.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/cli/main.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/envelope.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/errors.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/exceptions.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/human_fix.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/instruct.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/core/path.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/client.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/headers.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/http/polite.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/export.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/export_pdf.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/profile_store.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/shortlist.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/verdict.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/hunter/watches.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/_entry.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/mcp/server.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/account.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/listing.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/models/profile.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/search/__init__.py +0 -0
- {walla_cli-0.1.5 → walla_cli-0.1.6}/walla/search/api.py +0 -0
- {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.
|
|
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.
|
|
@@ -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"
|
|
@@ -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
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
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.
|
|
35
|
-
"fair": 0.
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|