slab-cli 0.12.0__tar.gz → 0.14.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.12.0 → slab_cli-0.14.0}/PKG-INFO +1 -1
- {slab_cli-0.12.0 → slab_cli-0.14.0}/pyproject.toml +1 -1
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/client.py +12 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/custom_sets.py +123 -24
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/registry.py +4 -3
- {slab_cli-0.12.0 → slab_cli-0.14.0}/README.md +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/__init__.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/banner.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/browse.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/__init__.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/breaks.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/catalog.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/collection.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/export.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/lots.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/pricing.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/setup.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/commands/update.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/config.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/context.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/display.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/flags.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/help.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/main.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/paging.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/picker.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/platforms/__init__.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/platforms/base.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/platforms/posix.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/platforms/windows.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/prompts.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/sources.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/theme.py +0 -0
- {slab_cli-0.12.0 → slab_cli-0.14.0}/src/slab_cli/updates.py +0 -0
|
@@ -40,6 +40,7 @@ from slab_schemas.collection import (
|
|
|
40
40
|
from slab_schemas.custom_sets import (
|
|
41
41
|
CustomSetCardAdd,
|
|
42
42
|
CustomSetCardOut,
|
|
43
|
+
CustomSetCardUpdate,
|
|
43
44
|
CustomSetCreate,
|
|
44
45
|
CustomSetDetail,
|
|
45
46
|
CustomSetOut,
|
|
@@ -316,6 +317,17 @@ class SlabClient:
|
|
|
316
317
|
)
|
|
317
318
|
return CustomSetCardOut.model_validate(resp.json())
|
|
318
319
|
|
|
320
|
+
def update_set_card(
|
|
321
|
+
self, collector_uuid: str, set_uuid: str, entry_uuid: str, payload: CustomSetCardUpdate
|
|
322
|
+
) -> CustomSetCardOut:
|
|
323
|
+
# Sparse by fields-set (not exclude_none): explicitly passing None CLEARS a qualifier.
|
|
324
|
+
resp = self._request(
|
|
325
|
+
"PATCH",
|
|
326
|
+
f"/collectors/{collector_uuid}/custom-sets/{set_uuid}/cards/{entry_uuid}",
|
|
327
|
+
json=payload.model_dump(mode="json", exclude_unset=True),
|
|
328
|
+
)
|
|
329
|
+
return CustomSetCardOut.model_validate(resp.json())
|
|
330
|
+
|
|
319
331
|
def delete_set_card(self, collector_uuid: str, set_uuid: str, entry_uuid: str) -> None:
|
|
320
332
|
self._request("DELETE", f"/collectors/{collector_uuid}/custom-sets/{set_uuid}/cards/{entry_uuid}")
|
|
321
333
|
|
|
@@ -236,7 +236,8 @@ def _build_dynamic_filter() -> dict | None:
|
|
|
236
236
|
# ---------------------------------------------------------------------------
|
|
237
237
|
|
|
238
238
|
def cmd_chase_add(args: list[str]) -> None:
|
|
239
|
-
"""Add
|
|
239
|
+
"""Add entries to a curated custom set — specific cards or player slots, batch mode like
|
|
240
|
+
`slab collection add`."""
|
|
240
241
|
collector = ctx.collector
|
|
241
242
|
client = ctx.client
|
|
242
243
|
|
|
@@ -388,7 +389,7 @@ def cmd_chase_view(args: list[str]) -> None:
|
|
|
388
389
|
|
|
389
390
|
|
|
390
391
|
def cmd_chase_edit(args: list[str]) -> None:
|
|
391
|
-
"""Edit a custom set's name, description, or
|
|
392
|
+
"""Edit a custom set's name, description, visibility, or (dynamic) match granularity."""
|
|
392
393
|
collector = ctx.collector
|
|
393
394
|
client = ctx.client
|
|
394
395
|
|
|
@@ -404,7 +405,10 @@ def cmd_chase_edit(args: list[str]) -> None:
|
|
|
404
405
|
|
|
405
406
|
console.print(f"\n Current name: {cs.name}")
|
|
406
407
|
console.print(f" Current description: {cs.description or '—'}")
|
|
407
|
-
console.print(f" Current visibility: {cs.visibility}
|
|
408
|
+
console.print(f" Current visibility: {cs.visibility}")
|
|
409
|
+
if cs.set_type == "dynamic":
|
|
410
|
+
console.print(f" Current match granularity: {detail.dynamic_match}")
|
|
411
|
+
console.print()
|
|
408
412
|
|
|
409
413
|
from slab_schemas.custom_sets import CustomSetUpdate
|
|
410
414
|
|
|
@@ -413,11 +417,20 @@ def cmd_chase_edit(args: list[str]) -> None:
|
|
|
413
417
|
vis = None
|
|
414
418
|
if confirm("Change visibility?", default=False):
|
|
415
419
|
vis = select_visibility()
|
|
420
|
+
new_match = None
|
|
421
|
+
if cs.set_type == "dynamic" and confirm(
|
|
422
|
+
"Change what counts as having an entry? (per printing / per card / per player)",
|
|
423
|
+
default=False,
|
|
424
|
+
):
|
|
425
|
+
new_match = select_dynamic_match(default=detail.dynamic_match or "any_printing")
|
|
426
|
+
if new_match == detail.dynamic_match:
|
|
427
|
+
new_match = None
|
|
416
428
|
|
|
417
429
|
update = CustomSetUpdate(
|
|
418
430
|
name=name,
|
|
419
431
|
description=description,
|
|
420
432
|
visibility=Visibility(vis) if vis else None,
|
|
433
|
+
dynamic_match=DynamicMatch(new_match) if new_match else None,
|
|
421
434
|
)
|
|
422
435
|
|
|
423
436
|
# Only send if something changed
|
|
@@ -427,6 +440,11 @@ def cmd_chase_edit(args: list[str]) -> None:
|
|
|
427
440
|
|
|
428
441
|
updated = client.update_custom_set(collector, cs.uuid, update)
|
|
429
442
|
console.print(f"[green]Updated![/green] {updated.name}")
|
|
443
|
+
if new_match:
|
|
444
|
+
console.print(
|
|
445
|
+
f" [dim]Membership redefined — the set now tracks one entry per "
|
|
446
|
+
f"{'player' if new_match == 'any_card' else 'card slot' if new_match == 'any_printing' else 'printing'}.[/dim]"
|
|
447
|
+
)
|
|
430
448
|
|
|
431
449
|
|
|
432
450
|
# ---------------------------------------------------------------------------
|
|
@@ -521,33 +539,114 @@ def cmd_chase_remove(args: list[str]) -> None:
|
|
|
521
539
|
console.print("[yellow]Can only remove cards from curated sets.[/yellow]")
|
|
522
540
|
return
|
|
523
541
|
|
|
524
|
-
|
|
525
|
-
if
|
|
526
|
-
|
|
527
|
-
|
|
542
|
+
entry = _pick_entry(client, collector, cs.uuid, "Remove which entry?")
|
|
543
|
+
if entry and confirm(f"Remove this entry? ({entry.uuid[:8]})", default=False):
|
|
544
|
+
client.delete_set_card(collector, cs.uuid, entry.uuid)
|
|
545
|
+
console.print("[green]Removed.[/green]")
|
|
528
546
|
|
|
529
|
-
choices = []
|
|
530
|
-
for entry in detail.cards:
|
|
531
|
-
card = entry.card
|
|
532
|
-
if card:
|
|
533
|
-
subjects = ", ".join(s.name for s in card.subjects)
|
|
534
|
-
label = f"{card.card_number} {subjects} — {entry.match_mode}"
|
|
535
|
-
if entry.serial_number is not None:
|
|
536
|
-
label += f" #{entry.serial_number}"
|
|
537
|
-
else:
|
|
538
|
-
label = f"{entry.subject or '—'} — any card"
|
|
539
|
-
owned = " ✓" if entry.owned else ""
|
|
540
|
-
choices.append(Choice(value=entry, name=f"{label}{owned}"))
|
|
541
547
|
|
|
542
|
-
|
|
543
|
-
|
|
548
|
+
def _entry_label(entry) -> str:
|
|
549
|
+
"""One line describing an entry — the card and its mode, or the player slot + qualifiers."""
|
|
550
|
+
card = entry.card
|
|
551
|
+
if card:
|
|
552
|
+
subjects = ", ".join(s.name for s in card.subjects)
|
|
553
|
+
label = f"{card.card_number} {subjects} — {entry.match_mode}"
|
|
554
|
+
if entry.serial_number is not None:
|
|
555
|
+
label += f" #{entry.serial_number}"
|
|
556
|
+
return label
|
|
557
|
+
quals = " · ".join(str(q) for q in (
|
|
558
|
+
f"{entry.subject_year}-{str(entry.subject_year + 1)[-2:]}" if entry.subject_year else None,
|
|
559
|
+
entry.subject_team, entry.subject_attribute) if q)
|
|
560
|
+
return f"{entry.subject or '—'} — any card" + (f" · {quals}" if quals else "")
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _pick_entry(client: SlabClient, collector: str, set_uuid: str, message: str):
|
|
564
|
+
"""Fetch the full entry list and pick one (None if empty or backed out)."""
|
|
565
|
+
detail = _fetch_full_detail(client, set_uuid, collector)
|
|
566
|
+
if not detail.cards:
|
|
567
|
+
console.print("[dim]This set has no entries.[/dim]")
|
|
568
|
+
return None
|
|
569
|
+
choices = [
|
|
570
|
+
Choice(value=e, name=f"{_entry_label(e)}{' ✓' if e.owned else ''}")
|
|
571
|
+
for e in detail.cards
|
|
572
|
+
]
|
|
573
|
+
return inquirer.select(
|
|
574
|
+
message=f"{message} ({len(detail.cards)} in set):",
|
|
544
575
|
choices=choices,
|
|
545
576
|
max_height="70%",
|
|
546
577
|
).execute()
|
|
547
578
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
579
|
+
|
|
580
|
+
# ---------------------------------------------------------------------------
|
|
581
|
+
# slab chase tune — retune one entry (match mode / player-slot qualifiers)
|
|
582
|
+
# ---------------------------------------------------------------------------
|
|
583
|
+
|
|
584
|
+
def cmd_chase_tune(args: list[str]) -> None:
|
|
585
|
+
"""Retune one entry of a curated set: a card entry's match mode, or a player slot's
|
|
586
|
+
season/team/attribute qualifiers. An entry's KIND (card vs player) is fixed — switch by
|
|
587
|
+
removing and re-adding."""
|
|
588
|
+
collector = ctx.collector
|
|
589
|
+
client = ctx.client
|
|
590
|
+
|
|
591
|
+
cs = _select_custom_set(client, collector, message="Which set to tune?")
|
|
592
|
+
if cs is None:
|
|
593
|
+
return
|
|
594
|
+
if cs.set_type != "curated":
|
|
595
|
+
console.print("[yellow]Dynamic sets have one set-wide granularity — use `slab chase edit`.[/yellow]")
|
|
596
|
+
return
|
|
597
|
+
detail = client.get_custom_set(cs.uuid, collector, limit=1)
|
|
598
|
+
if detail.creator_uuid != collector:
|
|
599
|
+
console.print("[yellow]You can only tune sets you created.[/yellow]")
|
|
600
|
+
return
|
|
601
|
+
|
|
602
|
+
entry = _pick_entry(client, collector, cs.uuid, "Tune which entry?")
|
|
603
|
+
if entry is None:
|
|
604
|
+
return
|
|
605
|
+
|
|
606
|
+
from slab_schemas.custom_sets import CustomSetCardUpdate
|
|
607
|
+
|
|
608
|
+
changes: dict = {}
|
|
609
|
+
if entry.card:
|
|
610
|
+
console.print(f"\n {_entry_label(entry)}")
|
|
611
|
+
new_mode = select_match_mode()
|
|
612
|
+
if new_mode != entry.match_mode:
|
|
613
|
+
changes["match_mode"] = MatchMode(new_mode)
|
|
614
|
+
if new_mode == "exact_serial":
|
|
615
|
+
serial = ask_int_optional("Which serial number?")
|
|
616
|
+
if serial is None:
|
|
617
|
+
console.print("[yellow]Serial number is required for exact_serial.[/yellow]")
|
|
618
|
+
return
|
|
619
|
+
if serial != entry.serial_number:
|
|
620
|
+
changes["serial_number"] = serial
|
|
621
|
+
else:
|
|
622
|
+
console.print(f"\n {_entry_label(entry)}")
|
|
623
|
+
console.print(" [dim]Retune the qualifiers — Enter keeps the current value, '-' clears it.[/dim]")
|
|
624
|
+
for field, label, cast in (
|
|
625
|
+
("subject_year", f"Season (current: {entry.subject_year or 'any'}):", int),
|
|
626
|
+
("subject_team", f"Team (current: {entry.subject_team or 'any'}):", str),
|
|
627
|
+
("subject_attribute", f"Attribute (current: {entry.subject_attribute or 'any'}):", str),
|
|
628
|
+
):
|
|
629
|
+
raw = ask_text_optional(label)
|
|
630
|
+
if not raw:
|
|
631
|
+
continue
|
|
632
|
+
if raw.strip() == "-":
|
|
633
|
+
if getattr(entry, field) is not None:
|
|
634
|
+
changes[field] = None
|
|
635
|
+
continue
|
|
636
|
+
try:
|
|
637
|
+
value = cast(raw.strip())
|
|
638
|
+
except ValueError:
|
|
639
|
+
console.print(f"[yellow] Skipped — not a valid value for {field}.[/yellow]")
|
|
640
|
+
continue
|
|
641
|
+
if value != getattr(entry, field):
|
|
642
|
+
changes[field] = value
|
|
643
|
+
|
|
644
|
+
if not changes:
|
|
645
|
+
console.print("[dim]No changes.[/dim]")
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
updated = client.update_set_card(collector, cs.uuid, entry.uuid, CustomSetCardUpdate(**changes))
|
|
649
|
+
console.print(f"[green]Tuned![/green] {_entry_label(updated)}")
|
|
551
650
|
|
|
552
651
|
|
|
553
652
|
# ---------------------------------------------------------------------------
|
|
@@ -94,13 +94,14 @@ GROUPS: list[Group] = [
|
|
|
94
94
|
]),
|
|
95
95
|
Group("chase", "custom sets & set-completion", [
|
|
96
96
|
Command("create", "Create a custom set (curated or dynamic)", custom_sets.cmd_chase_create),
|
|
97
|
-
Command("add", "Add cards to a curated set", custom_sets.cmd_chase_add),
|
|
97
|
+
Command("add", "Add cards or player slots to a curated set", custom_sets.cmd_chase_add),
|
|
98
98
|
Command("view", "View a set with completion + add missing", custom_sets.cmd_chase_view, "[uuid]"),
|
|
99
|
-
Command("edit", "Edit name, description, or
|
|
99
|
+
Command("edit", "Edit name, description, visibility, or granularity", custom_sets.cmd_chase_edit),
|
|
100
|
+
Command("tune", "Retune one entry — match mode or player-slot qualifiers", custom_sets.cmd_chase_tune),
|
|
100
101
|
Command("list", "Your sets (created + subscribed)", custom_sets.cmd_chase_list),
|
|
101
102
|
Command("find", "Discover public sets by name", custom_sets.cmd_chase_find, "[query]"),
|
|
102
103
|
Command("subscribe", "Subscribe to / unsubscribe from a public set", custom_sets.cmd_chase_subscribe, "[query]"),
|
|
103
|
-
Command("remove", "Remove
|
|
104
|
+
Command("remove", "Remove an entry from a set", custom_sets.cmd_chase_remove),
|
|
104
105
|
Command("delete", "Delete a set you created", custom_sets.cmd_chase_delete),
|
|
105
106
|
]),
|
|
106
107
|
Group("collector", "your identity", [
|
|
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
|