slab-cli 0.23.0__tar.gz → 0.24.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.
- {slab_cli-0.23.0 → slab_cli-0.24.0}/PKG-INFO +2 -2
- {slab_cli-0.23.0 → slab_cli-0.24.0}/pyproject.toml +2 -2
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/client.py +7 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/pricing.py +12 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/registry.py +1 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/display.py +46 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/README.md +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/__init__.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/banner.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/browse.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/__init__.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/breaks.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/catalog.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/collection.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/custom_sets.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/export.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/lots.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/setup.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/commands/update.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/config.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/context.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/flags.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/help.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/main.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/nav.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/paging.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/picker.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/platforms/__init__.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/platforms/base.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/platforms/posix.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/platforms/windows.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/prompts.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/sources.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/theme.py +0 -0
- {slab_cli-0.23.0 → slab_cli-0.24.0}/src/slab_cli/updates.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: slab-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal.
|
|
5
5
|
Author: dev_jeb
|
|
6
|
-
Requires-Dist: slab-schemas>=0.
|
|
6
|
+
Requires-Dist: slab-schemas>=0.41.0
|
|
7
7
|
Requires-Dist: httpx>=0.27
|
|
8
8
|
Requires-Dist: rich>=13.0
|
|
9
9
|
Requires-Dist: inquirerpy>=0.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "slab-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.24.0"
|
|
4
4
|
description = "CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{name = "dev_jeb"}]
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
# 0.34 reframes the grading desk from verdicts to break-even readings (BreakEvenRegion,
|
|
13
13
|
# GradingDesk.break_even) — the desk shows data, never tells the user to grade.
|
|
14
14
|
# 0.35 adds Liquidity/LiquidityLabel, which the market view's Sells column renders.
|
|
15
|
-
"slab-schemas>=0.
|
|
15
|
+
"slab-schemas>=0.41.0", # CardSerials (slab card serials)
|
|
16
16
|
"httpx>=0.27",
|
|
17
17
|
"rich>=13.0",
|
|
18
18
|
"InquirerPy>=0.3",
|
|
@@ -17,6 +17,7 @@ from slab_schemas.cards import (
|
|
|
17
17
|
SetSearchResult,
|
|
18
18
|
)
|
|
19
19
|
from slab_schemas.community import CommunityBoard
|
|
20
|
+
from slab_schemas.serials import CardSerials
|
|
20
21
|
from slab_schemas.collection import (
|
|
21
22
|
BreakCreate,
|
|
22
23
|
BreakOut,
|
|
@@ -151,6 +152,12 @@ class SlabClient:
|
|
|
151
152
|
)
|
|
152
153
|
return CollectionGradingDesk.model_validate(resp.json())
|
|
153
154
|
|
|
155
|
+
def get_card_serials(self, card_uuid: str) -> CardSerials:
|
|
156
|
+
"""Which numbered copies of this printing have surfaced — sales with a stated serial and
|
|
157
|
+
collection copies — grouped by serial (GET /cards/{uuid}/serials)."""
|
|
158
|
+
resp = self._request("GET", f"/cards/{card_uuid}/serials")
|
|
159
|
+
return CardSerials.model_validate(resp.json())
|
|
160
|
+
|
|
154
161
|
def get_card_price_history(
|
|
155
162
|
self,
|
|
156
163
|
card_uuid: str,
|
|
@@ -17,6 +17,7 @@ from ..display import (
|
|
|
17
17
|
render_portfolio,
|
|
18
18
|
render_price_history,
|
|
19
19
|
render_sealed_price_history,
|
|
20
|
+
render_serials,
|
|
20
21
|
render_set_market,
|
|
21
22
|
)
|
|
22
23
|
from ..paging import fetch_all_pages
|
|
@@ -71,6 +72,17 @@ def cmd_comps(args: list[str]) -> None:
|
|
|
71
72
|
render_comps(ctx.client.get_card_comps(card.uuid))
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
def cmd_serials(args: list[str]) -> None:
|
|
76
|
+
"""Which numbered copies of a card have surfaced. Same player → set → card flow as `price`,
|
|
77
|
+
then one row per serial seen: how many collections hold it, how many times it has sold, and
|
|
78
|
+
its last sale. Sales and held copies are shown apart — the collector who bought #24 on eBay
|
|
79
|
+
is one transfer seen from both sides. Unnumbered printings say so and stop."""
|
|
80
|
+
card = _pick_card(args)
|
|
81
|
+
if card is None:
|
|
82
|
+
return
|
|
83
|
+
render_serials(ctx.client.get_card_serials(card.uuid))
|
|
84
|
+
|
|
85
|
+
|
|
74
86
|
def _split_desk_flags(args: list[str]) -> tuple[list[str], float | None, str | None]:
|
|
75
87
|
"""Pull `--fee <amount>` and `--company <name>` out of a grading query. Hand-rolled (the
|
|
76
88
|
shared flag grammar is search-specific); an unparseable fee errors loudly rather than
|
|
@@ -61,6 +61,7 @@ GROUPS: list[Group] = [
|
|
|
61
61
|
Command("price", "Market price + 7d/30d trend", pricing.cmd_price, "<query>"),
|
|
62
62
|
Command("grade", "The grading math: payoffs per grade + break-even", pricing.cmd_grade, "<query> [--fee N]"),
|
|
63
63
|
Command("comps", "Recent raw sales behind the price", pricing.cmd_comps, "<query>"),
|
|
64
|
+
Command("serials", "Which numbered copies have surfaced (sales + collections)", pricing.cmd_serials, "<query>"),
|
|
64
65
|
Command("history", "Price history over time", pricing.cmd_history, "<query>"),
|
|
65
66
|
]),
|
|
66
67
|
Group("set", "browse and price sealed products", [
|
|
@@ -24,6 +24,7 @@ from slab_schemas.enums import BreakEvenRegion, Grade, LiquidityLabel, SealedFor
|
|
|
24
24
|
from slab_schemas.liquidity import Liquidity
|
|
25
25
|
from slab_schemas.pricing import CardComps, CardMarket, PortfolioSummary, SetTopCards
|
|
26
26
|
from slab_schemas.sealed import SealedPriceHistory, SealedProductOut
|
|
27
|
+
from slab_schemas.serials import CardSerials
|
|
27
28
|
from slab_schemas.timeseries import CardPriceHistory
|
|
28
29
|
|
|
29
30
|
from .browse import Page, browse
|
|
@@ -1254,6 +1255,51 @@ def render_set_market(s: SetOut, sealed: list[SealedProductOut], top: SetTopCard
|
|
|
1254
1255
|
console.print(" [slab.dim]Drill in with `slab card price` / `slab card comps`.[/]\n")
|
|
1255
1256
|
|
|
1256
1257
|
|
|
1258
|
+
def render_serials(s: CardSerials) -> None:
|
|
1259
|
+
"""Which numbered copies of a printing have surfaced — one row per serial, sales and held
|
|
1260
|
+
copies kept apart (the same transfer can be seen from both sides). The meaning text comes
|
|
1261
|
+
from the response's glossary, so the CLI, the site and the API say the same words."""
|
|
1262
|
+
subjects = ", ".join(s.subjects) or "—"
|
|
1263
|
+
console.print()
|
|
1264
|
+
console.print(_card_headline(s.card_number, subjects, s.set_name, _slot(s)))
|
|
1265
|
+
|
|
1266
|
+
if not s.print_run:
|
|
1267
|
+
console.print("\n [slab.dim]This printing isn't serial-numbered, so there are no serials to track.[/]\n")
|
|
1268
|
+
return
|
|
1269
|
+
|
|
1270
|
+
unseen = max(0, s.print_run - s.serials_seen)
|
|
1271
|
+
pct = f" ({s.coverage_pct:.0f}%)" if s.coverage_pct is not None else ""
|
|
1272
|
+
console.print(heading("Serials Seen", f"{s.serials_seen} of /{s.print_run}{pct} · {unseen} never surfaced"))
|
|
1273
|
+
if not s.serials:
|
|
1274
|
+
console.print(" [slab.dim]No numbered copy of this card has surfaced in a sale or a collection yet.[/]\n")
|
|
1275
|
+
return
|
|
1276
|
+
|
|
1277
|
+
table = slab_table([
|
|
1278
|
+
("Serial", {"justify": "right", "style": "slab.foil"}),
|
|
1279
|
+
("Held", {"justify": "right"}),
|
|
1280
|
+
("Sales", {"justify": "right"}),
|
|
1281
|
+
("Last sale", {"justify": "right", "style": "slab.foil"}),
|
|
1282
|
+
("Grade", {"style": "slab.dim"}),
|
|
1283
|
+
("Last seen", {"style": "slab.dim"}),
|
|
1284
|
+
])
|
|
1285
|
+
for sig in s.serials:
|
|
1286
|
+
last = sig.sales[-1] if sig.sales else None
|
|
1287
|
+
table.add_row(
|
|
1288
|
+
f"{sig.serial_number}/{s.print_run}",
|
|
1289
|
+
str(sig.held) if sig.held else "[slab.dim]—[/]",
|
|
1290
|
+
str(len(sig.sales)) if sig.sales else "[slab.dim]—[/]",
|
|
1291
|
+
money(last.sale_price) if last and last.sale_price is not None else "—",
|
|
1292
|
+
(last.grade_key or "—") if last else "—",
|
|
1293
|
+
sig.last_seen.isoformat() if sig.last_seen else "—",
|
|
1294
|
+
)
|
|
1295
|
+
console.print(table)
|
|
1296
|
+
for key in ("serials.coverage", "serials.held"):
|
|
1297
|
+
info = s.glossary.get(key)
|
|
1298
|
+
if info:
|
|
1299
|
+
console.print(f" [slab.dim]{info.label}: {info.summary}[/]")
|
|
1300
|
+
console.print()
|
|
1301
|
+
|
|
1302
|
+
|
|
1257
1303
|
def render_comps(c: CardComps) -> None:
|
|
1258
1304
|
"""Display recent comps (raw sales) for a catalog card — the evidence behind its FMV."""
|
|
1259
1305
|
subjects = ", ".join(c.subjects) or "—"
|
|
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
|