- CLAUDE.md + commit-push-pr: prefer gitea-mcp over tea CLI - Paper Dynasty: updated api_client, cli, distribute_packs - New skill: resume-tailoring - Plugins: updated marketplaces, blocklist, install counts - Settings and MCP config updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
732 lines
22 KiB
Python
Executable File
732 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Paper Dynasty CLI - Baseball Card Game Management
|
|
|
|
A command-line interface for the Paper Dynasty API, primarily for use with Claude Code.
|
|
|
|
Usage:
|
|
pd status
|
|
pd team list
|
|
pd team get SKB
|
|
pd team cards SKB
|
|
pd pack today
|
|
pd pack distribute --num 10
|
|
pd gauntlet list --event-id 8 --active
|
|
pd gauntlet cleanup Gauntlet-SKB --event-id 8 --yes
|
|
|
|
Environment:
|
|
API_TOKEN: Required. Bearer token for API authentication.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from typing import Annotated, List, Optional
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
# Import the existing API client from same directory
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from api_client import PaperDynastyAPI
|
|
|
|
# ============================================================================
|
|
# App Setup
|
|
# ============================================================================
|
|
|
|
app = typer.Typer(
|
|
name="pd",
|
|
help="Paper Dynasty Baseball Card Game CLI",
|
|
no_args_is_help=True,
|
|
)
|
|
team_app = typer.Typer(help="Team operations")
|
|
pack_app = typer.Typer(help="Pack operations")
|
|
gauntlet_app = typer.Typer(help="Gauntlet operations")
|
|
player_app = typer.Typer(help="Player operations")
|
|
|
|
app.add_typer(team_app, name="team")
|
|
app.add_typer(pack_app, name="pack")
|
|
app.add_typer(gauntlet_app, name="gauntlet")
|
|
app.add_typer(player_app, name="player")
|
|
|
|
console = Console()
|
|
|
|
|
|
class State:
|
|
"""Global state for API client and settings"""
|
|
|
|
api: Optional[PaperDynastyAPI] = None
|
|
json_output: bool = False
|
|
|
|
|
|
state = State()
|
|
|
|
|
|
# ============================================================================
|
|
# Output Helpers
|
|
# ============================================================================
|
|
|
|
|
|
def output_json(data):
|
|
"""Output data as formatted JSON"""
|
|
console.print_json(json.dumps(data, indent=2, default=str))
|
|
|
|
|
|
def output_table(
|
|
title: str, columns: List[str], rows: List[List], show_lines: bool = False
|
|
):
|
|
"""Output data as a rich table"""
|
|
table = Table(
|
|
title=title, show_header=True, header_style="bold cyan", show_lines=show_lines
|
|
)
|
|
for col in columns:
|
|
table.add_column(col)
|
|
for row in rows:
|
|
table.add_row(*[str(cell) if cell is not None else "" for cell in row])
|
|
console.print(table)
|
|
|
|
|
|
def handle_error(e: Exception, context: str = ""):
|
|
"""Graceful error handling with helpful messages"""
|
|
error_str = str(e)
|
|
if "401" in error_str:
|
|
console.print("[red]Error:[/red] Unauthorized. Check your API_TOKEN.")
|
|
elif "404" in error_str:
|
|
console.print(f"[red]Error:[/red] Not found. {context}")
|
|
elif "Connection" in error_str or "ConnectionError" in error_str:
|
|
console.print(
|
|
"[red]Error:[/red] Cannot connect to API. Check network and --env setting."
|
|
)
|
|
else:
|
|
console.print(f"[red]Error:[/red] {e}")
|
|
raise typer.Exit(1)
|
|
|
|
|
|
# ============================================================================
|
|
# Main Callback (Global Options)
|
|
# ============================================================================
|
|
|
|
|
|
@app.callback()
|
|
def main(
|
|
env: Annotated[
|
|
str, typer.Option("--env", help="Environment: prod or dev")
|
|
] = "prod",
|
|
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
verbose: Annotated[
|
|
bool, typer.Option("--verbose", "-v", help="Verbose output")
|
|
] = False,
|
|
):
|
|
"""Paper Dynasty Baseball Card Game CLI"""
|
|
state.api = PaperDynastyAPI(environment=env, verbose=verbose)
|
|
state.json_output = json_output
|
|
|
|
|
|
# ============================================================================
|
|
# Status & Health Commands
|
|
# ============================================================================
|
|
|
|
|
|
@app.command()
|
|
def status():
|
|
"""Show packs opened today summary"""
|
|
try:
|
|
result = state.api.get_packs_opened_today()
|
|
|
|
if state.json_output:
|
|
output_json(result)
|
|
return
|
|
|
|
console.print(
|
|
f"\n[bold cyan]Packs Opened Today ({result['date']})[/bold cyan]\n"
|
|
)
|
|
console.print(f"[bold]Total:[/bold] {result['total']} packs\n")
|
|
|
|
if result["teams"]:
|
|
rows = []
|
|
for t in result["teams"]:
|
|
rows.append([t["abbrev"], t["name"], t["packs"]])
|
|
output_table("By Team", ["Abbrev", "Team", "Packs"], rows)
|
|
else:
|
|
console.print("[dim]No packs opened today[/dim]")
|
|
|
|
if result.get("note"):
|
|
console.print(f"\n[yellow]Note:[/yellow] {result['note']}")
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
@app.command()
|
|
def health():
|
|
"""Check API health status"""
|
|
try:
|
|
# Try to list teams as a health check
|
|
teams = state.api.list_teams()
|
|
console.print(f"[green]API is healthy[/green] ({state.api.base_url})")
|
|
console.print(f"[dim]Found {len(teams)} teams[/dim]")
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
# ============================================================================
|
|
# Team Commands
|
|
# ============================================================================
|
|
|
|
|
|
@team_app.command("list")
|
|
def team_list(
|
|
season: Annotated[
|
|
Optional[int], typer.Option("--season", "-s", help="Filter by season")
|
|
] = None,
|
|
):
|
|
"""List all teams"""
|
|
try:
|
|
teams = state.api.list_teams(season=season)
|
|
|
|
if state.json_output:
|
|
output_json(teams)
|
|
return
|
|
|
|
if not teams:
|
|
console.print("[yellow]No teams found[/yellow]")
|
|
return
|
|
|
|
# Filter out gauntlet teams for cleaner display
|
|
regular_teams = [t for t in teams if "Gauntlet" not in t.get("abbrev", "")]
|
|
|
|
rows = []
|
|
for t in regular_teams:
|
|
rows.append(
|
|
[
|
|
t["abbrev"],
|
|
t.get("sname", ""),
|
|
t.get("season", ""),
|
|
t.get("wallet", 0),
|
|
t.get("ranking", "N/A"),
|
|
"AI" if t.get("is_ai") else "Human",
|
|
]
|
|
)
|
|
|
|
title = "Teams"
|
|
if season:
|
|
title += f" - Season {season}"
|
|
output_table(
|
|
title, ["Abbrev", "Name", "Season", "Wallet", "Rank", "Type"], rows
|
|
)
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
@team_app.command("get")
|
|
def team_get(
|
|
abbrev: Annotated[str, typer.Argument(help="Team abbreviation")],
|
|
):
|
|
"""Get team details"""
|
|
try:
|
|
team = state.api.get_team(abbrev=abbrev.upper())
|
|
|
|
if state.json_output:
|
|
output_json(team)
|
|
return
|
|
|
|
panel = Panel(
|
|
f"[bold]ID:[/bold] {team['id']}\n"
|
|
f"[bold]Abbreviation:[/bold] {team['abbrev']}\n"
|
|
f"[bold]Short Name:[/bold] {team.get('sname', 'N/A')}\n"
|
|
f"[bold]Full Name:[/bold] {team.get('lname', 'N/A')}\n"
|
|
f"[bold]Season:[/bold] {team.get('season', 'N/A')}\n"
|
|
f"[bold]Wallet:[/bold] ${team.get('wallet', 0)}\n"
|
|
f"[bold]Ranking:[/bold] {team.get('ranking', 'N/A')}\n"
|
|
f"[bold]Type:[/bold] {'AI' if team.get('is_ai') else 'Human'}",
|
|
title=f"Team: {team.get('lname', abbrev)}",
|
|
border_style="green",
|
|
)
|
|
console.print(panel)
|
|
|
|
except ValueError as e:
|
|
console.print(f"[red]Error:[/red] {e}")
|
|
raise typer.Exit(1)
|
|
except Exception as e:
|
|
handle_error(e, f"Team '{abbrev}' may not exist.")
|
|
|
|
|
|
@team_app.command("cards")
|
|
def team_cards(
|
|
abbrev: Annotated[str, typer.Argument(help="Team abbreviation")],
|
|
limit: Annotated[int, typer.Option("--limit", "-n", help="Max cards to show")] = 50,
|
|
):
|
|
"""List team's cards"""
|
|
try:
|
|
team = state.api.get_team(abbrev=abbrev.upper())
|
|
cards = state.api.list_cards(team_id=team["id"])
|
|
|
|
if state.json_output:
|
|
output_json(cards)
|
|
return
|
|
|
|
if not cards:
|
|
console.print(f"[yellow]Team {abbrev} has no cards[/yellow]")
|
|
return
|
|
|
|
rows = []
|
|
for c in cards[:limit]:
|
|
player = c.get("player", {})
|
|
rows.append(
|
|
[
|
|
c["id"],
|
|
player.get("p_name", "Unknown"),
|
|
player.get("rarity", ""),
|
|
c.get("value", 0),
|
|
]
|
|
)
|
|
|
|
output_table(
|
|
f"Cards for {team.get('lname', abbrev)} ({len(cards)} total)",
|
|
["Card ID", "Player", "Rarity", "Value"],
|
|
rows,
|
|
)
|
|
|
|
if len(cards) > limit:
|
|
console.print(
|
|
f"\n[dim]Showing {limit} of {len(cards)} cards. Use --limit to see more.[/dim]"
|
|
)
|
|
|
|
except ValueError as e:
|
|
console.print(f"[red]Error:[/red] {e}")
|
|
raise typer.Exit(1)
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
# ============================================================================
|
|
# Pack Commands
|
|
# ============================================================================
|
|
|
|
|
|
@pack_app.command("list")
|
|
def pack_list(
|
|
team: Annotated[
|
|
Optional[str], typer.Option("--team", "-t", help="Filter by team abbrev")
|
|
] = None,
|
|
opened: Annotated[
|
|
Optional[bool],
|
|
typer.Option("--opened/--unopened", help="Filter by opened status"),
|
|
] = None,
|
|
limit: Annotated[int, typer.Option("--limit", "-n", help="Max packs to show")] = 50,
|
|
):
|
|
"""List packs"""
|
|
try:
|
|
team_id = None
|
|
team_name = None
|
|
if team:
|
|
team_obj = state.api.get_team(abbrev=team.upper())
|
|
team_id = team_obj["id"]
|
|
team_name = team_obj.get("sname", team)
|
|
|
|
packs = state.api.list_packs(
|
|
team_id=team_id, opened=opened, new_to_old=True, limit=limit
|
|
)
|
|
|
|
if state.json_output:
|
|
output_json(packs)
|
|
return
|
|
|
|
if not packs:
|
|
console.print("[yellow]No packs found[/yellow]")
|
|
return
|
|
|
|
rows = []
|
|
for p in packs:
|
|
pack_team = p.get("team", {})
|
|
pack_type = p.get("pack_type", {})
|
|
is_opened = "Yes" if p.get("open_time") else "No"
|
|
rows.append(
|
|
[
|
|
p["id"],
|
|
pack_team.get("abbrev", "N/A"),
|
|
pack_type.get("name", "Unknown"),
|
|
is_opened,
|
|
]
|
|
)
|
|
|
|
title = "Packs"
|
|
if team_name:
|
|
title += f" - {team_name}"
|
|
if opened is True:
|
|
title += " (Opened)"
|
|
elif opened is False:
|
|
title += " (Unopened)"
|
|
|
|
output_table(title, ["Pack ID", "Team", "Type", "Opened"], rows)
|
|
|
|
except ValueError as e:
|
|
console.print(f"[red]Error:[/red] {e}")
|
|
raise typer.Exit(1)
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
@pack_app.command("today")
|
|
def pack_today():
|
|
"""Show packs opened today analytics"""
|
|
# Reuse status command
|
|
status()
|
|
|
|
|
|
@pack_app.command("distribute")
|
|
def pack_distribute(
|
|
num: Annotated[
|
|
int, typer.Option("--num", "-n", help="Number of packs per team")
|
|
] = 5,
|
|
exclude: Annotated[
|
|
Optional[List[str]],
|
|
typer.Option("--exclude", "-x", help="Team abbrevs to exclude"),
|
|
] = None,
|
|
pack_type: Annotated[
|
|
int,
|
|
typer.Option(
|
|
"--pack-type",
|
|
help="1=Standard, 2=Starter, 3=Premium, 4=Check-In, 5=MVP, 6=All Star, 7=Mario, 8=Team Choice, 9=Promo Choice",
|
|
),
|
|
] = 1,
|
|
cardset: Annotated[
|
|
Optional[int],
|
|
typer.Option(
|
|
"--cardset", "-c", help="Cardset ID (required for Promo Choice packs)"
|
|
),
|
|
] = None,
|
|
dry_run: Annotated[
|
|
bool, typer.Option("--dry-run", help="Show what would be done")
|
|
] = False,
|
|
):
|
|
"""Distribute packs to all human teams"""
|
|
try:
|
|
if dry_run:
|
|
# Get qualifying teams to show preview
|
|
current = state.api.get("current")
|
|
season = current["season"]
|
|
all_teams = state.api.list_teams(season=season)
|
|
|
|
exclude_upper = [e.upper() for e in (exclude or [])]
|
|
qualifying = [
|
|
t
|
|
for t in all_teams
|
|
if not t["is_ai"]
|
|
and "gauntlet" not in t["abbrev"].lower()
|
|
and t["abbrev"].upper() not in exclude_upper
|
|
]
|
|
|
|
console.print(
|
|
f"\n[bold cyan]Pack Distribution Preview (DRY RUN)[/bold cyan]\n"
|
|
)
|
|
console.print(f"[bold]Packs per team:[/bold] {num}")
|
|
console.print(f"[bold]Pack type:[/bold] {pack_type}")
|
|
if cardset is not None:
|
|
console.print(f"[bold]Cardset ID:[/bold] {cardset}")
|
|
console.print(f"[bold]Teams:[/bold] {len(qualifying)}")
|
|
console.print(f"[bold]Total packs:[/bold] {num * len(qualifying)}")
|
|
|
|
if exclude:
|
|
console.print(f"[bold]Excluded:[/bold] {', '.join(exclude)}")
|
|
|
|
console.print("\n[bold]Qualifying teams:[/bold]")
|
|
for t in qualifying:
|
|
console.print(f" - {t['abbrev']}: {t['sname']}")
|
|
|
|
return
|
|
|
|
result = state.api.distribute_packs(
|
|
num_packs=num,
|
|
exclude_team_abbrev=exclude,
|
|
pack_type_id=pack_type,
|
|
cardset_id=cardset,
|
|
)
|
|
|
|
if state.json_output:
|
|
output_json(result)
|
|
return
|
|
|
|
console.print(f"\n[green]Distribution complete![/green]")
|
|
console.print(f"[bold]Total packs:[/bold] {result['total_packs']}")
|
|
console.print(f"[bold]Teams:[/bold] {result['teams_count']}")
|
|
|
|
if exclude:
|
|
console.print(f"[bold]Excluded:[/bold] {', '.join(exclude)}")
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
# ============================================================================
|
|
# Gauntlet Commands
|
|
# ============================================================================
|
|
|
|
|
|
@gauntlet_app.command("list")
|
|
def gauntlet_list(
|
|
event_id: Annotated[
|
|
Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID")
|
|
] = None,
|
|
active: Annotated[
|
|
bool, typer.Option("--active", "-a", help="Only active runs")
|
|
] = False,
|
|
):
|
|
"""List gauntlet runs"""
|
|
try:
|
|
runs = state.api.list_gauntlet_runs(event_id=event_id, active_only=active)
|
|
|
|
if state.json_output:
|
|
output_json(runs)
|
|
return
|
|
|
|
if not runs:
|
|
console.print("[yellow]No gauntlet runs found[/yellow]")
|
|
return
|
|
|
|
rows = []
|
|
for r in runs:
|
|
team = r.get("team", {})
|
|
is_active = "Active" if r.get("ended") is None else "Ended"
|
|
gauntlet = r.get("gauntlet", {})
|
|
rows.append(
|
|
[
|
|
r["id"],
|
|
team.get("abbrev", "N/A"),
|
|
r.get("wins", 0),
|
|
r.get("losses", 0),
|
|
gauntlet.get("id", "N/A"),
|
|
is_active,
|
|
]
|
|
)
|
|
|
|
title = "Gauntlet Runs"
|
|
if event_id:
|
|
title += f" - Event {event_id}"
|
|
if active:
|
|
title += " (Active Only)"
|
|
|
|
output_table(title, ["Run ID", "Team", "W", "L", "Event", "Status"], rows)
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
@gauntlet_app.command("teams")
|
|
def gauntlet_teams(
|
|
event_id: Annotated[
|
|
Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID")
|
|
] = None,
|
|
active: Annotated[
|
|
bool, typer.Option("--active", "-a", help="Only teams with active runs")
|
|
] = False,
|
|
):
|
|
"""List gauntlet teams"""
|
|
try:
|
|
teams = state.api.find_gauntlet_teams(event_id=event_id, active_only=active)
|
|
|
|
if state.json_output:
|
|
output_json(teams)
|
|
return
|
|
|
|
if not teams:
|
|
console.print("[yellow]No gauntlet teams found[/yellow]")
|
|
return
|
|
|
|
rows = []
|
|
for t in teams:
|
|
run = t.get("active_run") or t.get("run", {})
|
|
wins = run.get("wins", "-") if run else "-"
|
|
losses = run.get("losses", "-") if run else "-"
|
|
rows.append([t["id"], t["abbrev"], t.get("sname", ""), wins, losses])
|
|
|
|
title = "Gauntlet Teams"
|
|
if active:
|
|
title += " (Active)"
|
|
|
|
output_table(title, ["Team ID", "Abbrev", "Name", "W", "L"], rows)
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
@gauntlet_app.command("cleanup")
|
|
def gauntlet_cleanup(
|
|
team_abbrev: Annotated[
|
|
str, typer.Argument(help="Team abbreviation (e.g., Gauntlet-SKB)")
|
|
],
|
|
event_id: Annotated[
|
|
int, typer.Option("--event-id", "-e", help="Event ID (required)")
|
|
],
|
|
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
|
|
):
|
|
"""Clean up a gauntlet team (wipe cards, delete packs, end run)"""
|
|
try:
|
|
# Find the team
|
|
team = state.api.get_team(abbrev=team_abbrev)
|
|
team_id = team["id"]
|
|
|
|
# Get cards and packs count
|
|
cards = state.api.list_cards(team_id=team_id)
|
|
packs = state.api.list_packs(team_id=team_id, opened=False)
|
|
|
|
# Find active run
|
|
runs = state.api.list_gauntlet_runs(
|
|
event_id=event_id, team_id=team_id, active_only=True
|
|
)
|
|
active_run = runs[0] if runs else None
|
|
|
|
console.print(f"\n[bold cyan]Gauntlet Cleanup: {team_abbrev}[/bold cyan]\n")
|
|
console.print(f"[bold]Team ID:[/bold] {team_id}")
|
|
console.print(f"[bold]Cards to wipe:[/bold] {len(cards)}")
|
|
console.print(f"[bold]Packs to delete:[/bold] {len(packs)}")
|
|
console.print(
|
|
f"[bold]Active run:[/bold] {'Yes (ID: ' + str(active_run['id']) + ')' if active_run else 'No'}"
|
|
)
|
|
|
|
if not yes:
|
|
console.print("\n[yellow]This is a destructive operation![/yellow]")
|
|
console.print("Use --yes flag to confirm.")
|
|
raise typer.Exit(0)
|
|
|
|
# Perform cleanup
|
|
results = []
|
|
|
|
# 1. Wipe cards
|
|
if cards:
|
|
state.api.wipe_team_cards(team_id)
|
|
results.append(f"Wiped {len(cards)} cards")
|
|
|
|
# 2. Delete packs
|
|
for pack in packs:
|
|
state.api.delete_pack(pack["id"])
|
|
if packs:
|
|
results.append(f"Deleted {len(packs)} packs")
|
|
|
|
# 3. End gauntlet run
|
|
if active_run:
|
|
state.api.end_gauntlet_run(active_run["id"])
|
|
results.append(f"Ended run {active_run['id']}")
|
|
|
|
console.print(f"\n[green]Cleanup complete![/green]")
|
|
for r in results:
|
|
console.print(f" - {r}")
|
|
|
|
except ValueError as e:
|
|
console.print(f"[red]Error:[/red] {e}")
|
|
raise typer.Exit(1)
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
# ============================================================================
|
|
# Player Commands
|
|
# ============================================================================
|
|
|
|
|
|
@player_app.command("get")
|
|
def player_get(
|
|
player_id: Annotated[int, typer.Argument(help="Player ID")],
|
|
):
|
|
"""Get player by ID"""
|
|
try:
|
|
player = state.api.get_player(player_id=player_id)
|
|
|
|
if state.json_output:
|
|
output_json(player)
|
|
return
|
|
|
|
# Get positions
|
|
positions = []
|
|
for i in range(1, 9):
|
|
pos = player.get(f"pos_{i}")
|
|
if pos:
|
|
positions.append(pos)
|
|
|
|
cardset = player.get("cardset", {})
|
|
rarity = player.get("rarity", {})
|
|
rarity_name = rarity.get("name", "N/A") if isinstance(rarity, dict) else rarity
|
|
|
|
panel = Panel(
|
|
f"[bold]ID:[/bold] {player['player_id']}\n"
|
|
f"[bold]Name:[/bold] {player.get('p_name', 'Unknown')}\n"
|
|
f"[bold]Rarity:[/bold] {rarity_name}\n"
|
|
f"[bold]Cost:[/bold] {player.get('cost', 0)}\n"
|
|
f"[bold]Positions:[/bold] {', '.join(positions) if positions else 'N/A'}\n"
|
|
f"[bold]Cardset:[/bold] {cardset.get('name', 'N/A')} (ID: {cardset.get('id', 'N/A')})\n"
|
|
f"[bold]Hand:[/bold] {player.get('hand', 'N/A')}",
|
|
title=f"Player: {player.get('p_name', 'Unknown')}",
|
|
border_style="blue",
|
|
)
|
|
console.print(panel)
|
|
|
|
except Exception as e:
|
|
handle_error(e, f"Player ID {player_id} may not exist.")
|
|
|
|
|
|
@player_app.command("list")
|
|
def player_list(
|
|
rarity: Annotated[
|
|
Optional[str], typer.Option("--rarity", "-r", help="Filter by rarity")
|
|
] = None,
|
|
cardset: Annotated[
|
|
Optional[int], typer.Option("--cardset", "-c", help="Filter by cardset ID")
|
|
] = None,
|
|
limit: Annotated[
|
|
int, typer.Option("--limit", "-n", help="Max players to show")
|
|
] = 50,
|
|
):
|
|
"""List players"""
|
|
try:
|
|
players = state.api.list_players(cardset_id=cardset, rarity=rarity)
|
|
|
|
if state.json_output:
|
|
output_json(players)
|
|
return
|
|
|
|
if not players:
|
|
console.print("[yellow]No players found[/yellow]")
|
|
return
|
|
|
|
rows = []
|
|
for p in players[:limit]:
|
|
cs = p.get("cardset", {})
|
|
rarity = p.get("rarity", {})
|
|
rarity_name = rarity.get("name", "") if isinstance(rarity, dict) else rarity
|
|
rows.append(
|
|
[
|
|
p["player_id"],
|
|
p.get("p_name", "Unknown"),
|
|
rarity_name,
|
|
p.get("cost", 0),
|
|
cs.get("name", "N/A"),
|
|
]
|
|
)
|
|
|
|
title = "Players"
|
|
if rarity:
|
|
title += f" - {rarity}"
|
|
if cardset:
|
|
title += f" - Cardset {cardset}"
|
|
|
|
output_table(title, ["ID", "Name", "Rarity", "Cost", "Cardset"], rows)
|
|
|
|
if len(players) > limit:
|
|
console.print(
|
|
f"\n[dim]Showing {limit} of {len(players)} players. Use --limit to see more.[/dim]"
|
|
)
|
|
|
|
except Exception as e:
|
|
handle_error(e)
|
|
|
|
|
|
# ============================================================================
|
|
# Entry Point
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
app()
|