fitzroy 1.2.0 → 1.3.1

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.
package/dist/cli.js CHANGED
@@ -10,6 +10,12 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/errors.ts
13
+ function aflwUnsupportedError(source) {
14
+ return new UnsupportedSourceError(
15
+ `AFLW data is not available from ${source}. Use --source afl-api for AFLW data.`,
16
+ source
17
+ );
18
+ }
13
19
  var AflApiError, ScrapeError, UnsupportedSourceError, ValidationError;
14
20
  var init_errors = __esm({
15
21
  "src/lib/errors.ts"() {
@@ -45,6 +51,47 @@ var init_errors = __esm({
45
51
  }
46
52
  });
47
53
 
54
+ // src/cli/error-boundary.ts
55
+ import pc from "picocolors";
56
+ function formatError(error) {
57
+ if (error instanceof ValidationError && error.issues) {
58
+ const issueLines = error.issues.map((i) => ` ${pc.yellow(i.path)}: ${i.message}`);
59
+ return `${pc.red("Validation error:")}
60
+ ${issueLines.join("\n")}`;
61
+ }
62
+ if (error instanceof AflApiError) {
63
+ const status = error.statusCode ? ` (HTTP ${error.statusCode})` : "";
64
+ return `${pc.red("AFL API error:")} ${error.message}${status}`;
65
+ }
66
+ if (error instanceof ScrapeError) {
67
+ const source = error.source ? ` [${error.source}]` : "";
68
+ return `${pc.red("Scrape error:")} ${error.message}${source}`;
69
+ }
70
+ if (error instanceof UnsupportedSourceError) {
71
+ return `${pc.red("Unsupported source:")} ${error.message}`;
72
+ }
73
+ if (error instanceof Error) {
74
+ return `${pc.red("Error:")} ${error.message}`;
75
+ }
76
+ return `${pc.red("Error:")} ${String(error)}`;
77
+ }
78
+ function withErrorBoundary(fn) {
79
+ return async (ctx) => {
80
+ try {
81
+ await fn(ctx);
82
+ } catch (error) {
83
+ console.error(formatError(error));
84
+ process.exit(1);
85
+ }
86
+ };
87
+ }
88
+ var init_error_boundary = __esm({
89
+ "src/cli/error-boundary.ts"() {
90
+ "use strict";
91
+ init_errors();
92
+ }
93
+ });
94
+
48
95
  // src/lib/result.ts
49
96
  function ok(data) {
50
97
  return { success: true, data };
@@ -117,6 +164,10 @@ function parseAflTablesDate(dateStr) {
117
164
  }
118
165
  return null;
119
166
  }
167
+ function resolveDefaultSeason(competition = "AFLM") {
168
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
169
+ return competition === "AFLW" ? year - 1 : year;
170
+ }
120
171
  function buildUtcDate(year, monthStr, day) {
121
172
  const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
122
173
  if (monthIndex === void 0) {
@@ -168,7 +219,7 @@ function normaliseTeamName(raw) {
168
219
  const trimmed = raw.trim();
169
220
  return ALIAS_MAP.get(trimmed.toLowerCase()) ?? trimmed;
170
221
  }
171
- var TEAM_ALIASES, AFL_SENIOR_TEAMS, ALIAS_MAP;
222
+ var TEAM_ALIASES, AFL_SENIOR_TEAMS, ALIAS_MAP, AFL_API_TEAM_IDS;
172
223
  var init_team_mapping = __esm({
173
224
  "src/lib/team-mapping.ts"() {
174
225
  "use strict";
@@ -241,6 +292,26 @@ var init_team_mapping = __esm({
241
292
  }
242
293
  return map;
243
294
  })();
295
+ AFL_API_TEAM_IDS = /* @__PURE__ */ new Map([
296
+ ["CD_T10", "Adelaide Crows"],
297
+ ["CD_T20", "Brisbane Lions"],
298
+ ["CD_T30", "Carlton"],
299
+ ["CD_T40", "Collingwood"],
300
+ ["CD_T50", "Essendon"],
301
+ ["CD_T60", "Fremantle"],
302
+ ["CD_T70", "Geelong Cats"],
303
+ ["CD_T1000", "Gold Coast Suns"],
304
+ ["CD_T1010", "GWS Giants"],
305
+ ["CD_T80", "Hawthorn"],
306
+ ["CD_T90", "Melbourne"],
307
+ ["CD_T100", "North Melbourne"],
308
+ ["CD_T110", "Port Adelaide"],
309
+ ["CD_T120", "Richmond"],
310
+ ["CD_T130", "St Kilda"],
311
+ ["CD_T160", "Sydney Swans"],
312
+ ["CD_T150", "West Coast Eagles"],
313
+ ["CD_T140", "Western Bulldogs"]
314
+ ]);
244
315
  }
245
316
  });
246
317
 
@@ -506,6 +577,14 @@ var init_footywire_player_stats = __esm({
506
577
  function inferRoundType(roundName) {
507
578
  return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
508
579
  }
580
+ function finalsRoundNumber(headerText, lastHARound) {
581
+ const lower = headerText.toLowerCase();
582
+ if (lower.includes("qualifying") || lower.includes("elimination")) return lastHARound + 1;
583
+ if (lower.includes("semi")) return lastHARound + 2;
584
+ if (lower.includes("preliminary")) return lastHARound + 3;
585
+ if (lower.includes("grand")) return lastHARound + 4;
586
+ return lastHARound + 1;
587
+ }
509
588
  function toMatchStatus(raw) {
510
589
  switch (raw) {
511
590
  case "CONCLUDED":
@@ -595,6 +674,7 @@ function parseMatchList(html, year) {
595
674
  const $ = cheerio2.load(html);
596
675
  const results = [];
597
676
  let currentRound = 0;
677
+ let lastHARound = 0;
598
678
  let currentRoundType = "HomeAndAway";
599
679
  $("tr").each((_i, row) => {
600
680
  const roundHeader = $(row).find("td[colspan='7']");
@@ -604,6 +684,11 @@ function parseMatchList(html, year) {
604
684
  const roundMatch = /Round\s+(\d+)/i.exec(text);
605
685
  if (roundMatch?.[1]) {
606
686
  currentRound = Number.parseInt(roundMatch[1], 10);
687
+ if (currentRoundType === "HomeAndAway") {
688
+ lastHARound = currentRound;
689
+ }
690
+ } else if (currentRoundType === "Finals") {
691
+ currentRound = finalsRoundNumber(text, lastHARound);
607
692
  }
608
693
  return;
609
694
  }
@@ -674,6 +759,7 @@ function parseFixtureList(html, year) {
674
759
  const $ = cheerio2.load(html);
675
760
  const fixtures = [];
676
761
  let currentRound = 0;
762
+ let lastHARound = 0;
677
763
  let currentRoundType = "HomeAndAway";
678
764
  let gameNumber = 0;
679
765
  $("tr").each((_i, row) => {
@@ -684,6 +770,11 @@ function parseFixtureList(html, year) {
684
770
  const roundMatch = /Round\s+(\d+)/i.exec(text);
685
771
  if (roundMatch?.[1]) {
686
772
  currentRound = Number.parseInt(roundMatch[1], 10);
773
+ if (currentRoundType === "HomeAndAway") {
774
+ lastHARound = currentRound;
775
+ }
776
+ } else if (currentRoundType === "Finals") {
777
+ currentRound = finalsRoundNumber(text, lastHARound);
687
778
  }
688
779
  return;
689
780
  }
@@ -1431,7 +1522,15 @@ var init_validation = __esm({
1431
1522
  captain: z.boolean().optional(),
1432
1523
  playerJumperNumber: z.number().optional()
1433
1524
  }).passthrough();
1434
- statNum = z.number().nullable().optional();
1525
+ statNum = z.union([
1526
+ z.number(),
1527
+ z.string().transform((s) => {
1528
+ if (s === "" || s === "-") return null;
1529
+ const n = Number(s);
1530
+ return Number.isNaN(n) ? null : n;
1531
+ }),
1532
+ z.boolean().transform((b) => b ? 1 : 0)
1533
+ ]).nullable().optional();
1435
1534
  PlayerGameStatsSchema = z.object({
1436
1535
  goals: statNum,
1437
1536
  behinds: statNum,
@@ -1509,8 +1608,8 @@ var init_validation = __esm({
1509
1608
  teamId: z.string(),
1510
1609
  playerStats: z.object({
1511
1610
  stats: PlayerGameStatsSchema,
1512
- timeOnGroundPercentage: z.number().nullable().optional()
1513
- }).passthrough()
1611
+ timeOnGroundPercentage: statNum
1612
+ }).passthrough().nullable().optional()
1514
1613
  }).passthrough();
1515
1614
  PlayerStatsListSchema = z.object({
1516
1615
  homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
@@ -2221,12 +2320,14 @@ function toFixture(item, season, fallbackRoundNumber, competition) {
2221
2320
  async function fetchFixture(query) {
2222
2321
  const competition = query.competition ?? "AFLM";
2223
2322
  if (query.source === "squiggle") {
2323
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2224
2324
  const client2 = new SquiggleClient();
2225
2325
  const result = await client2.fetchGames(query.season, query.round ?? void 0);
2226
2326
  if (!result.success) return result;
2227
2327
  return ok(transformSquiggleGamesToFixture(result.data.games, query.season));
2228
2328
  }
2229
2329
  if (query.source === "footywire") {
2330
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
2230
2331
  const fwClient = new FootyWireClient();
2231
2332
  const result = await fwClient.fetchSeasonFixture(query.season);
2232
2333
  if (!result.success) return result;
@@ -2417,6 +2518,7 @@ function parseSeasonPage(html, year) {
2417
2518
  const results = [];
2418
2519
  let currentRound = 0;
2419
2520
  let currentRoundType = "HomeAndAway";
2521
+ let lastHARound = 0;
2420
2522
  let matchCounter = 0;
2421
2523
  $("table").each((_i, table) => {
2422
2524
  const $table = $(table);
@@ -2426,10 +2528,14 @@ function parseSeasonPage(html, year) {
2426
2528
  if (roundMatch?.[1] && border !== "1") {
2427
2529
  currentRound = Number.parseInt(roundMatch[1], 10);
2428
2530
  currentRoundType = inferRoundType(text);
2531
+ if (currentRoundType === "HomeAndAway") {
2532
+ lastHARound = currentRound;
2533
+ }
2429
2534
  return;
2430
2535
  }
2431
2536
  if (border !== "1" && inferRoundType(text) === "Finals") {
2432
2537
  currentRoundType = "Finals";
2538
+ currentRound = finalsRoundNumber(text, lastHARound);
2433
2539
  return;
2434
2540
  }
2435
2541
  if (border !== "1") return;
@@ -2939,6 +3045,7 @@ var init_ladder = __esm({
2939
3045
  async function fetchLadder(query) {
2940
3046
  const competition = query.competition ?? "AFLM";
2941
3047
  if (query.source === "squiggle") {
3048
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2942
3049
  const client2 = new SquiggleClient();
2943
3050
  const result = await client2.fetchStandings(query.season, query.round ?? void 0);
2944
3051
  if (!result.success) return result;
@@ -2950,6 +3057,7 @@ async function fetchLadder(query) {
2950
3057
  });
2951
3058
  }
2952
3059
  if (query.source === "afl-tables") {
3060
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
2953
3061
  const atClient = new AflTablesClient();
2954
3062
  const resultsResult = await atClient.fetchSeasonResults(query.season);
2955
3063
  if (!resultsResult.success) return resultsResult;
@@ -3114,6 +3222,7 @@ async function fetchMatchResults(query) {
3114
3222
  return ok(transformMatchItems(itemsResult.data, query.season, competition));
3115
3223
  }
3116
3224
  case "footywire": {
3225
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3117
3226
  const client = new FootyWireClient();
3118
3227
  const result = await client.fetchSeasonResults(query.season);
3119
3228
  if (!result.success) return result;
@@ -3123,6 +3232,7 @@ async function fetchMatchResults(query) {
3123
3232
  return result;
3124
3233
  }
3125
3234
  case "afl-tables": {
3235
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3126
3236
  const client = new AflTablesClient();
3127
3237
  const result = await client.fetchSeasonResults(query.season);
3128
3238
  if (!result.success) return result;
@@ -3132,6 +3242,7 @@ async function fetchMatchResults(query) {
3132
3242
  return result;
3133
3243
  }
3134
3244
  case "squiggle": {
3245
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
3135
3246
  const client = new SquiggleClient();
3136
3247
  const result = await client.fetchGames(query.season, query.round ?? void 0, 100);
3137
3248
  if (!result.success) return result;
@@ -3170,7 +3281,7 @@ async function resolveTeamId(client, teamName, competition) {
3170
3281
  async function fetchFromAflApi(query) {
3171
3282
  const client = new AflApiClient();
3172
3283
  const competition = query.competition ?? "AFLM";
3173
- const season = query.season ?? (/* @__PURE__ */ new Date()).getFullYear();
3284
+ const season = query.season ?? resolveDefaultSeason(competition);
3174
3285
  const [teamIdResult, seasonResult] = await Promise.all([
3175
3286
  resolveTeamId(client, query.team, competition),
3176
3287
  client.resolveCompSeason(competition, season)
@@ -3193,8 +3304,8 @@ async function fetchFromAflApi(query) {
3193
3304
  jumperNumber: p.jumperNumber ?? null,
3194
3305
  position: p.position ?? null,
3195
3306
  dateOfBirth: p.player.dateOfBirth ?? null,
3196
- heightCm: p.player.heightInCm ?? null,
3197
- weightKg: p.player.weightInKg ?? null,
3307
+ heightCm: p.player.heightInCm || null,
3308
+ weightKg: p.player.weightInKg || null,
3198
3309
  gamesPlayed: null,
3199
3310
  goals: null,
3200
3311
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
@@ -3208,8 +3319,9 @@ async function fetchFromAflApi(query) {
3208
3319
  return ok(players);
3209
3320
  }
3210
3321
  async function fetchFromFootyWire(query) {
3211
- const client = new FootyWireClient();
3212
3322
  const competition = query.competition ?? "AFLM";
3323
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3324
+ const client = new FootyWireClient();
3213
3325
  const teamName = normaliseTeamName(query.team);
3214
3326
  const result = await client.fetchPlayerList(teamName);
3215
3327
  if (!result.success) return result;
@@ -3221,8 +3333,9 @@ async function fetchFromFootyWire(query) {
3221
3333
  return ok(players);
3222
3334
  }
3223
3335
  async function fetchFromAflTables(query) {
3224
- const client = new AflTablesClient();
3225
3336
  const competition = query.competition ?? "AFLM";
3337
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3338
+ const client = new AflTablesClient();
3226
3339
  const teamName = normaliseTeamName(query.team);
3227
3340
  const result = await client.fetchPlayerList(teamName);
3228
3341
  if (!result.success) return result;
@@ -3253,6 +3366,7 @@ async function fetchPlayerDetails(query) {
3253
3366
  var init_player_details = __esm({
3254
3367
  "src/api/player-details.ts"() {
3255
3368
  "use strict";
3369
+ init_date_utils();
3256
3370
  init_errors();
3257
3371
  init_result();
3258
3372
  init_team_mapping();
@@ -3268,80 +3382,82 @@ function toNullable(value) {
3268
3382
  }
3269
3383
  function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3270
3384
  const inner = item.player.player.player;
3271
- const stats = item.playerStats.stats;
3272
- const clearances = stats.clearances;
3385
+ const stats = item.playerStats?.stats;
3386
+ const clearances = stats?.clearances;
3273
3387
  return {
3274
3388
  matchId,
3275
3389
  season,
3276
3390
  roundNumber,
3277
- team: normaliseTeamName(teamIdMap?.get(item.teamId) ?? item.teamId),
3391
+ team: normaliseTeamName(
3392
+ teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3393
+ ),
3278
3394
  competition,
3279
3395
  playerId: inner.playerId,
3280
3396
  givenName: inner.playerName.givenName,
3281
3397
  surname: inner.playerName.surname,
3282
3398
  displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
3283
3399
  jumperNumber: item.player.jumperNumber ?? null,
3284
- kicks: toNullable(stats.kicks),
3285
- handballs: toNullable(stats.handballs),
3286
- disposals: toNullable(stats.disposals),
3287
- marks: toNullable(stats.marks),
3288
- goals: toNullable(stats.goals),
3289
- behinds: toNullable(stats.behinds),
3290
- tackles: toNullable(stats.tackles),
3291
- hitouts: toNullable(stats.hitouts),
3292
- freesFor: toNullable(stats.freesFor),
3293
- freesAgainst: toNullable(stats.freesAgainst),
3294
- contestedPossessions: toNullable(stats.contestedPossessions),
3295
- uncontestedPossessions: toNullable(stats.uncontestedPossessions),
3296
- contestedMarks: toNullable(stats.contestedMarks),
3297
- intercepts: toNullable(stats.intercepts),
3400
+ kicks: toNullable(stats?.kicks),
3401
+ handballs: toNullable(stats?.handballs),
3402
+ disposals: toNullable(stats?.disposals),
3403
+ marks: toNullable(stats?.marks),
3404
+ goals: toNullable(stats?.goals),
3405
+ behinds: toNullable(stats?.behinds),
3406
+ tackles: toNullable(stats?.tackles),
3407
+ hitouts: toNullable(stats?.hitouts),
3408
+ freesFor: toNullable(stats?.freesFor),
3409
+ freesAgainst: toNullable(stats?.freesAgainst),
3410
+ contestedPossessions: toNullable(stats?.contestedPossessions),
3411
+ uncontestedPossessions: toNullable(stats?.uncontestedPossessions),
3412
+ contestedMarks: toNullable(stats?.contestedMarks),
3413
+ intercepts: toNullable(stats?.intercepts),
3298
3414
  centreClearances: toNullable(clearances?.centreClearances),
3299
3415
  stoppageClearances: toNullable(clearances?.stoppageClearances),
3300
3416
  totalClearances: toNullable(clearances?.totalClearances),
3301
- inside50s: toNullable(stats.inside50s),
3302
- rebound50s: toNullable(stats.rebound50s),
3303
- clangers: toNullable(stats.clangers),
3304
- turnovers: toNullable(stats.turnovers),
3305
- onePercenters: toNullable(stats.onePercenters),
3306
- bounces: toNullable(stats.bounces),
3307
- goalAssists: toNullable(stats.goalAssists),
3308
- disposalEfficiency: toNullable(stats.disposalEfficiency),
3309
- metresGained: toNullable(stats.metresGained),
3310
- goalAccuracy: toNullable(stats.goalAccuracy),
3311
- marksInside50: toNullable(stats.marksInside50),
3312
- tacklesInside50: toNullable(stats.tacklesInside50),
3313
- shotsAtGoal: toNullable(stats.shotsAtGoal),
3314
- scoreInvolvements: toNullable(stats.scoreInvolvements),
3315
- totalPossessions: toNullable(stats.totalPossessions),
3316
- timeOnGroundPercentage: toNullable(item.playerStats.timeOnGroundPercentage),
3317
- ratingPoints: toNullable(stats.ratingPoints),
3318
- dreamTeamPoints: toNullable(stats.dreamTeamPoints),
3319
- effectiveDisposals: toNullable(stats.extendedStats?.effectiveDisposals),
3320
- effectiveKicks: toNullable(stats.extendedStats?.effectiveKicks),
3321
- kickEfficiency: toNullable(stats.extendedStats?.kickEfficiency),
3322
- kickToHandballRatio: toNullable(stats.extendedStats?.kickToHandballRatio),
3323
- pressureActs: toNullable(stats.extendedStats?.pressureActs),
3324
- defHalfPressureActs: toNullable(stats.extendedStats?.defHalfPressureActs),
3325
- spoils: toNullable(stats.extendedStats?.spoils),
3326
- hitoutsToAdvantage: toNullable(stats.extendedStats?.hitoutsToAdvantage),
3327
- hitoutWinPercentage: toNullable(stats.extendedStats?.hitoutWinPercentage),
3328
- hitoutToAdvantageRate: toNullable(stats.extendedStats?.hitoutToAdvantageRate),
3329
- groundBallGets: toNullable(stats.extendedStats?.groundBallGets),
3330
- f50GroundBallGets: toNullable(stats.extendedStats?.f50GroundBallGets),
3331
- interceptMarks: toNullable(stats.extendedStats?.interceptMarks),
3332
- marksOnLead: toNullable(stats.extendedStats?.marksOnLead),
3333
- contestedPossessionRate: toNullable(stats.extendedStats?.contestedPossessionRate),
3334
- contestOffOneOnOnes: toNullable(stats.extendedStats?.contestOffOneOnOnes),
3335
- contestOffWins: toNullable(stats.extendedStats?.contestOffWins),
3336
- contestOffWinsPercentage: toNullable(stats.extendedStats?.contestOffWinsPercentage),
3337
- contestDefOneOnOnes: toNullable(stats.extendedStats?.contestDefOneOnOnes),
3338
- contestDefLosses: toNullable(stats.extendedStats?.contestDefLosses),
3339
- contestDefLossPercentage: toNullable(stats.extendedStats?.contestDefLossPercentage),
3340
- centreBounceAttendances: toNullable(stats.extendedStats?.centreBounceAttendances),
3341
- kickins: toNullable(stats.extendedStats?.kickins),
3342
- kickinsPlayon: toNullable(stats.extendedStats?.kickinsPlayon),
3343
- ruckContests: toNullable(stats.extendedStats?.ruckContests),
3344
- scoreLaunches: toNullable(stats.extendedStats?.scoreLaunches),
3417
+ inside50s: toNullable(stats?.inside50s),
3418
+ rebound50s: toNullable(stats?.rebound50s),
3419
+ clangers: toNullable(stats?.clangers),
3420
+ turnovers: toNullable(stats?.turnovers),
3421
+ onePercenters: toNullable(stats?.onePercenters),
3422
+ bounces: toNullable(stats?.bounces),
3423
+ goalAssists: toNullable(stats?.goalAssists),
3424
+ disposalEfficiency: toNullable(stats?.disposalEfficiency),
3425
+ metresGained: toNullable(stats?.metresGained),
3426
+ goalAccuracy: toNullable(stats?.goalAccuracy),
3427
+ marksInside50: toNullable(stats?.marksInside50),
3428
+ tacklesInside50: toNullable(stats?.tacklesInside50),
3429
+ shotsAtGoal: toNullable(stats?.shotsAtGoal),
3430
+ scoreInvolvements: toNullable(stats?.scoreInvolvements),
3431
+ totalPossessions: toNullable(stats?.totalPossessions),
3432
+ timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3433
+ ratingPoints: toNullable(stats?.ratingPoints),
3434
+ dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3435
+ effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3436
+ effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
3437
+ kickEfficiency: toNullable(stats?.extendedStats?.kickEfficiency),
3438
+ kickToHandballRatio: toNullable(stats?.extendedStats?.kickToHandballRatio),
3439
+ pressureActs: toNullable(stats?.extendedStats?.pressureActs),
3440
+ defHalfPressureActs: toNullable(stats?.extendedStats?.defHalfPressureActs),
3441
+ spoils: toNullable(stats?.extendedStats?.spoils),
3442
+ hitoutsToAdvantage: toNullable(stats?.extendedStats?.hitoutsToAdvantage),
3443
+ hitoutWinPercentage: toNullable(stats?.extendedStats?.hitoutWinPercentage),
3444
+ hitoutToAdvantageRate: toNullable(stats?.extendedStats?.hitoutToAdvantageRate),
3445
+ groundBallGets: toNullable(stats?.extendedStats?.groundBallGets),
3446
+ f50GroundBallGets: toNullable(stats?.extendedStats?.f50GroundBallGets),
3447
+ interceptMarks: toNullable(stats?.extendedStats?.interceptMarks),
3448
+ marksOnLead: toNullable(stats?.extendedStats?.marksOnLead),
3449
+ contestedPossessionRate: toNullable(stats?.extendedStats?.contestedPossessionRate),
3450
+ contestOffOneOnOnes: toNullable(stats?.extendedStats?.contestOffOneOnOnes),
3451
+ contestOffWins: toNullable(stats?.extendedStats?.contestOffWins),
3452
+ contestOffWinsPercentage: toNullable(stats?.extendedStats?.contestOffWinsPercentage),
3453
+ contestDefOneOnOnes: toNullable(stats?.extendedStats?.contestDefOneOnOnes),
3454
+ contestDefLosses: toNullable(stats?.extendedStats?.contestDefLosses),
3455
+ contestDefLossPercentage: toNullable(stats?.extendedStats?.contestDefLossPercentage),
3456
+ centreBounceAttendances: toNullable(stats?.extendedStats?.centreBounceAttendances),
3457
+ kickins: toNullable(stats?.extendedStats?.kickins),
3458
+ kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3459
+ ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3460
+ scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3345
3461
  source
3346
3462
  };
3347
3463
  }
@@ -3373,11 +3489,11 @@ async function fetchPlayerStats(query) {
3373
3489
  client.fetchPlayerStats(query.matchId)
3374
3490
  ]);
3375
3491
  if (!statsResult.success) return statsResult;
3376
- const teamIdMap2 = /* @__PURE__ */ new Map();
3492
+ const teamIdMap2 = new Map(AFL_API_TEAM_IDS);
3377
3493
  if (rosterResult.success) {
3378
3494
  const match = rosterResult.data.match;
3379
- teamIdMap2.set(match.homeTeamId, match.homeTeam.name);
3380
- teamIdMap2.set(match.awayTeamId, match.awayTeam.name);
3495
+ teamIdMap2.set(match.homeTeamId, normaliseTeamName(match.homeTeam.name));
3496
+ teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
3381
3497
  }
3382
3498
  return ok(
3383
3499
  transformPlayerStats(
@@ -3387,7 +3503,7 @@ async function fetchPlayerStats(query) {
3387
3503
  query.round ?? 0,
3388
3504
  competition,
3389
3505
  "afl-api",
3390
- teamIdMap2.size > 0 ? teamIdMap2 : void 0
3506
+ teamIdMap2
3391
3507
  )
3392
3508
  );
3393
3509
  }
@@ -3430,6 +3546,7 @@ async function fetchPlayerStats(query) {
3430
3546
  return ok(allStats);
3431
3547
  }
3432
3548
  case "footywire": {
3549
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3433
3550
  const fwClient = new FootyWireClient();
3434
3551
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3435
3552
  if (!idsResult.success) return idsResult;
@@ -3459,6 +3576,7 @@ async function fetchPlayerStats(query) {
3459
3576
  return ok(allStats);
3460
3577
  }
3461
3578
  case "afl-tables": {
3579
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3462
3580
  const atClient = new AflTablesClient();
3463
3581
  const atResult = await atClient.fetchSeasonPlayerStats(query.season);
3464
3582
  if (!atResult.success) return atResult;
@@ -3477,6 +3595,7 @@ var init_player_stats2 = __esm({
3477
3595
  init_concurrency();
3478
3596
  init_errors();
3479
3597
  init_result();
3598
+ init_team_mapping();
3480
3599
  init_afl_api();
3481
3600
  init_afl_tables();
3482
3601
  init_footywire();
@@ -3494,7 +3613,39 @@ async function fetchTeamStats(query) {
3494
3613
  }
3495
3614
  case "afl-tables": {
3496
3615
  const client = new AflTablesClient();
3497
- return client.fetchTeamStats(query.season);
3616
+ const statsResult = await client.fetchTeamStats(query.season);
3617
+ if (!statsResult.success) return statsResult;
3618
+ const needsGp = statsResult.data.some((e) => e.gamesPlayed === 0);
3619
+ const gpMap = /* @__PURE__ */ new Map();
3620
+ if (needsGp) {
3621
+ const resultsResult = await client.fetchSeasonResults(query.season);
3622
+ if (resultsResult.success) {
3623
+ for (const m of resultsResult.data) {
3624
+ const home = normaliseTeamName(m.homeTeam);
3625
+ const away = normaliseTeamName(m.awayTeam);
3626
+ gpMap.set(home, (gpMap.get(home) ?? 0) + 1);
3627
+ gpMap.set(away, (gpMap.get(away) ?? 0) + 1);
3628
+ }
3629
+ }
3630
+ }
3631
+ const enriched = statsResult.data.map((entry) => ({
3632
+ ...entry,
3633
+ gamesPlayed: gpMap.get(normaliseTeamName(entry.team)) ?? entry.gamesPlayed
3634
+ }));
3635
+ if (summaryType === "averages") {
3636
+ return ok(
3637
+ enriched.map((entry) => ({
3638
+ ...entry,
3639
+ stats: Object.fromEntries(
3640
+ Object.entries(entry.stats).map(([k, v]) => [
3641
+ k,
3642
+ entry.gamesPlayed > 0 ? +(v / entry.gamesPlayed).toFixed(1) : 0
3643
+ ])
3644
+ )
3645
+ }))
3646
+ );
3647
+ }
3648
+ return ok(enriched);
3498
3649
  }
3499
3650
  case "afl-api":
3500
3651
  case "squiggle":
@@ -3513,6 +3664,7 @@ var init_team_stats = __esm({
3513
3664
  "use strict";
3514
3665
  init_errors();
3515
3666
  init_result();
3667
+ init_team_mapping();
3516
3668
  init_afl_tables();
3517
3669
  init_footywire();
3518
3670
  }
@@ -3522,19 +3674,30 @@ var init_team_stats = __esm({
3522
3674
  function teamTypeForComp(comp) {
3523
3675
  return comp === "AFLW" ? "WOMEN" : "MEN";
3524
3676
  }
3525
- async function fetchTeams(query) {
3526
- const client = new AflApiClient();
3527
- const teamType = query?.teamType ?? teamTypeForComp(query?.competition ?? "AFLM");
3528
- const result = await client.fetchTeams(teamType);
3529
- if (!result.success) return result;
3530
- const competition = query?.competition ?? "AFLM";
3531
- const teams = result.data.map((t) => ({
3677
+ function toTeams(data, competition) {
3678
+ return data.map((t) => ({
3532
3679
  teamId: String(t.id),
3533
3680
  name: normaliseTeamName(t.name),
3534
3681
  abbreviation: t.abbreviation ?? "",
3535
3682
  competition
3536
3683
  })).filter((t) => AFL_SENIOR_TEAMS.has(t.name));
3537
- return ok(teams);
3684
+ }
3685
+ async function fetchTeams(query) {
3686
+ const client = new AflApiClient();
3687
+ if (!query?.competition && !query?.teamType) {
3688
+ const [menResult, womenResult] = await Promise.all([
3689
+ client.fetchTeams("MEN"),
3690
+ client.fetchTeams("WOMEN")
3691
+ ]);
3692
+ if (!menResult.success) return menResult;
3693
+ if (!womenResult.success) return womenResult;
3694
+ return ok([...toTeams(menResult.data, "AFLM"), ...toTeams(womenResult.data, "AFLW")]);
3695
+ }
3696
+ const competition = query?.competition ?? "AFLM";
3697
+ const teamType = query?.teamType ?? teamTypeForComp(competition);
3698
+ const result = await client.fetchTeams(teamType);
3699
+ if (!result.success) return result;
3700
+ return ok(toTeams(result.data, competition));
3538
3701
  }
3539
3702
  async function fetchSquad(query) {
3540
3703
  const client = new AflApiClient();
@@ -3555,8 +3718,8 @@ async function fetchSquad(query) {
3555
3718
  jumperNumber: p.jumperNumber ?? null,
3556
3719
  position: p.position ?? null,
3557
3720
  dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
3558
- heightCm: p.player.heightInCm ?? null,
3559
- weightKg: p.player.weightInKg ?? null,
3721
+ heightCm: p.player.heightInCm || null,
3722
+ weightKg: p.player.weightInKg || null,
3560
3723
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
3561
3724
  draftPosition: p.player.draftPosition ? Number.parseInt(p.player.draftPosition, 10) || null : null,
3562
3725
  draftType: p.player.draftType ?? null,
@@ -3897,10 +4060,6 @@ function validateOptionalSeason(raw) {
3897
4060
  if (raw != null) return validateSeason(raw);
3898
4061
  return void 0;
3899
4062
  }
3900
- function resolveDefaultSeason(competition = "AFLM") {
3901
- const year = (/* @__PURE__ */ new Date()).getFullYear();
3902
- return competition === "AFLW" ? year - 1 : year;
3903
- }
3904
4063
  function validateRound(raw) {
3905
4064
  const round = Number(raw);
3906
4065
  if (Number.isNaN(round) || !Number.isInteger(round) || round < 0) {
@@ -3935,6 +4094,15 @@ function validateSource(raw) {
3935
4094
  }
3936
4095
  throw new Error(`Invalid source: "${raw}" \u2014 valid sources are: ${VALID_SOURCES.join(", ")}`);
3937
4096
  }
4097
+ function validateSummary(raw) {
4098
+ const lower = raw.toLowerCase();
4099
+ if (VALID_SUMMARIES.includes(lower)) {
4100
+ return lower;
4101
+ }
4102
+ throw new Error(
4103
+ `Invalid summary type: "${raw}" \u2014 valid values are: ${VALID_SUMMARIES.join(", ")}`
4104
+ );
4105
+ }
3938
4106
  function resolveTeamIdentifier(raw, teams) {
3939
4107
  const trimmed = raw.trim();
3940
4108
  if (/^\d+$/.test(trimmed)) {
@@ -3964,24 +4132,30 @@ function resolveMatchByTeam(teamSearch, matchItems) {
3964
4132
  return singleMatch.match.matchId;
3965
4133
  }
3966
4134
  if (matches.length === 0) {
3967
- const available = matchItems.map((item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`).join(", ");
4135
+ const available = matchItems.map(
4136
+ (item) => `${normaliseTeamName(item.match.homeTeam.name)} vs ${normaliseTeamName(item.match.awayTeam.name)}`
4137
+ ).join(", ");
3968
4138
  throw new Error(
3969
4139
  `No match found for "${teamSearch}" in this round. Available matches: ${available}`
3970
4140
  );
3971
4141
  }
3972
- const ambiguous = matches.map((item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`).join(", ");
4142
+ const ambiguous = matches.map(
4143
+ (item) => `${normaliseTeamName(item.match.homeTeam.name)} vs ${normaliseTeamName(item.match.awayTeam.name)}`
4144
+ ).join(", ");
3973
4145
  throw new Error(
3974
4146
  `Multiple matches found for "${teamSearch}": ${ambiguous}. Please be more specific.`
3975
4147
  );
3976
4148
  }
3977
- var VALID_SOURCES, VALID_COMPETITIONS, VALID_FORMATS;
4149
+ var VALID_SOURCES, VALID_COMPETITIONS, VALID_FORMATS, VALID_SUMMARIES;
3978
4150
  var init_validation2 = __esm({
3979
4151
  "src/cli/validation.ts"() {
3980
4152
  "use strict";
3981
4153
  init_team_mapping();
4154
+ init_date_utils();
3982
4155
  VALID_SOURCES = ["afl-api", "footywire", "afl-tables", "squiggle"];
3983
4156
  VALID_COMPETITIONS = ["AFLM", "AFLW"];
3984
4157
  VALID_FORMATS = ["table", "json", "csv"];
4158
+ VALID_SUMMARIES = ["totals", "averages"];
3985
4159
  }
3986
4160
  });
3987
4161
 
@@ -3996,6 +4170,7 @@ var init_matches = __esm({
3996
4170
  "src/cli/commands/matches.ts"() {
3997
4171
  "use strict";
3998
4172
  init_index();
4173
+ init_error_boundary();
3999
4174
  init_flags();
4000
4175
  init_formatters();
4001
4176
  init_ui();
@@ -4021,7 +4196,7 @@ var init_matches = __esm({
4021
4196
  ...COMPETITION_FLAG,
4022
4197
  ...OUTPUT_FLAGS
4023
4198
  },
4024
- async run({ args }) {
4199
+ run: withErrorBoundary(async ({ args }) => {
4025
4200
  const season = validateSeason(args.season);
4026
4201
  const round = args.round ? validateRound(args.round) : void 0;
4027
4202
  const source = validateSource(args.source);
@@ -4044,7 +4219,7 @@ var init_matches = __esm({
4044
4219
  columns: DEFAULT_COLUMNS
4045
4220
  };
4046
4221
  console.log(formatOutput(data, formatOptions));
4047
- }
4222
+ })
4048
4223
  });
4049
4224
  }
4050
4225
  });
@@ -4168,7 +4343,7 @@ async function resolveMatchOrPrompt(query, matchItems) {
4168
4343
  }
4169
4344
  const labelledItems = matchItems.map((item) => ({
4170
4345
  item,
4171
- label: `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`
4346
+ label: `${normaliseTeamName(item.match.homeTeam.name)} vs ${normaliseTeamName(item.match.awayTeam.name)}`
4172
4347
  }));
4173
4348
  const matches = fuzzySearch(query, labelledItems, (l) => l.label, {
4174
4349
  maxResults: 5,
@@ -4185,21 +4360,21 @@ async function resolveMatchOrPrompt(query, matchItems) {
4185
4360
  const seen = new Set(matches.map((m) => m.item.item.match.matchId));
4186
4361
  for (const m of homeMatches) {
4187
4362
  if (!seen.has(m.item.match.matchId)) {
4188
- const label = `${m.item.match.homeTeam.name} vs ${m.item.match.awayTeam.name}`;
4363
+ const label = `${normaliseTeamName(m.item.match.homeTeam.name)} vs ${normaliseTeamName(m.item.match.awayTeam.name)}`;
4189
4364
  matches.push({ item: { item: m.item, label }, score: m.score });
4190
4365
  seen.add(m.item.match.matchId);
4191
4366
  }
4192
4367
  }
4193
4368
  for (const m of awayMatches) {
4194
4369
  if (!seen.has(m.item.match.matchId)) {
4195
- const label = `${m.item.match.homeTeam.name} vs ${m.item.match.awayTeam.name}`;
4370
+ const label = `${normaliseTeamName(m.item.match.homeTeam.name)} vs ${normaliseTeamName(m.item.match.awayTeam.name)}`;
4196
4371
  matches.push({ item: { item: m.item, label }, score: m.score });
4197
4372
  seen.add(m.item.match.matchId);
4198
4373
  }
4199
4374
  }
4200
4375
  matches.sort((a, b) => a.score - b.score);
4201
4376
  const available = matchItems.map(
4202
- (item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`
4377
+ (item) => `${normaliseTeamName(item.match.homeTeam.name)} vs ${normaliseTeamName(item.match.awayTeam.name)}`
4203
4378
  );
4204
4379
  return disambiguate(
4205
4380
  query,
@@ -4259,6 +4434,7 @@ var init_stats = __esm({
4259
4434
  init_index();
4260
4435
  init_fuzzy();
4261
4436
  init_afl_api();
4437
+ init_error_boundary();
4262
4438
  init_flags();
4263
4439
  init_formatters();
4264
4440
  init_resolvers();
@@ -4288,7 +4464,7 @@ var init_stats = __esm({
4288
4464
  ...PLAYER_FLAG,
4289
4465
  ...OUTPUT_FLAGS
4290
4466
  },
4291
- async run({ args }) {
4467
+ run: withErrorBoundary(async ({ args }) => {
4292
4468
  const season = validateSeason(args.season);
4293
4469
  const round = args.round ? validateRound(args.round) : void 0;
4294
4470
  const source = validateSource(args.source);
@@ -4331,7 +4507,7 @@ var init_stats = __esm({
4331
4507
  columns: DEFAULT_COLUMNS2
4332
4508
  };
4333
4509
  console.log(formatOutput(data, formatOptions));
4334
- }
4510
+ })
4335
4511
  });
4336
4512
  }
4337
4513
  });
@@ -4347,6 +4523,7 @@ var init_fixture2 = __esm({
4347
4523
  "src/cli/commands/fixture.ts"() {
4348
4524
  "use strict";
4349
4525
  init_index();
4526
+ init_error_boundary();
4350
4527
  init_flags();
4351
4528
  init_formatters();
4352
4529
  init_ui();
@@ -4370,7 +4547,7 @@ var init_fixture2 = __esm({
4370
4547
  ...COMPETITION_FLAG,
4371
4548
  ...OUTPUT_FLAGS
4372
4549
  },
4373
- async run({ args }) {
4550
+ run: withErrorBoundary(async ({ args }) => {
4374
4551
  const season = validateSeason(args.season);
4375
4552
  const round = args.round ? validateRound(args.round) : void 0;
4376
4553
  const source = validateSource(args.source);
@@ -4393,7 +4570,7 @@ var init_fixture2 = __esm({
4393
4570
  columns: DEFAULT_COLUMNS3
4394
4571
  };
4395
4572
  console.log(formatOutput(data, formatOptions));
4396
- }
4573
+ })
4397
4574
  });
4398
4575
  }
4399
4576
  });
@@ -4409,6 +4586,7 @@ var init_ladder3 = __esm({
4409
4586
  "src/cli/commands/ladder.ts"() {
4410
4587
  "use strict";
4411
4588
  init_index();
4589
+ init_error_boundary();
4412
4590
  init_flags();
4413
4591
  init_formatters();
4414
4592
  init_ui();
@@ -4434,7 +4612,7 @@ var init_ladder3 = __esm({
4434
4612
  ...COMPETITION_FLAG,
4435
4613
  ...OUTPUT_FLAGS
4436
4614
  },
4437
- async run({ args }) {
4615
+ run: withErrorBoundary(async ({ args }) => {
4438
4616
  const season = validateSeason(args.season);
4439
4617
  const round = args.round ? validateRound(args.round) : void 0;
4440
4618
  const source = validateSource(args.source);
@@ -4459,7 +4637,7 @@ var init_ladder3 = __esm({
4459
4637
  columns: DEFAULT_COLUMNS4
4460
4638
  };
4461
4639
  console.log(formatOutput(data.entries, formatOptions));
4462
- }
4640
+ })
4463
4641
  });
4464
4642
  }
4465
4643
  });
@@ -4498,6 +4676,7 @@ var init_lineup3 = __esm({
4498
4676
  "use strict";
4499
4677
  init_index();
4500
4678
  init_afl_api();
4679
+ init_error_boundary();
4501
4680
  init_flags();
4502
4681
  init_formatters();
4503
4682
  init_resolvers();
@@ -4524,7 +4703,7 @@ var init_lineup3 = __esm({
4524
4703
  ...COMPETITION_FLAG,
4525
4704
  ...OUTPUT_FLAGS
4526
4705
  },
4527
- async run({ args }) {
4706
+ run: withErrorBoundary(async ({ args }) => {
4528
4707
  const season = validateSeason(args.season);
4529
4708
  const round = validateRound(args.round);
4530
4709
  const source = validateSource(args.source);
@@ -4561,7 +4740,7 @@ var init_lineup3 = __esm({
4561
4740
  } else {
4562
4741
  console.log(formatOutput(flattenLineups(data), formatOptions));
4563
4742
  }
4564
- }
4743
+ })
4565
4744
  });
4566
4745
  }
4567
4746
  });
@@ -4577,6 +4756,7 @@ var init_squad = __esm({
4577
4756
  "src/cli/commands/squad.ts"() {
4578
4757
  "use strict";
4579
4758
  init_index();
4759
+ init_error_boundary();
4580
4760
  init_flags();
4581
4761
  init_formatters();
4582
4762
  init_resolvers();
@@ -4600,7 +4780,7 @@ var init_squad = __esm({
4600
4780
  ...COMPETITION_FLAG,
4601
4781
  ...OUTPUT_FLAGS
4602
4782
  },
4603
- async run({ args }) {
4783
+ run: withErrorBoundary(async ({ args }) => {
4604
4784
  const season = validateSeason(args.season);
4605
4785
  const competition = validateCompetition(args.competition);
4606
4786
  const format = validateFormat(args.format);
@@ -4629,7 +4809,7 @@ var init_squad = __esm({
4629
4809
  columns: DEFAULT_COLUMNS6
4630
4810
  };
4631
4811
  console.log(formatOutput(data.players, formatOptions));
4632
- }
4812
+ })
4633
4813
  });
4634
4814
  }
4635
4815
  });
@@ -4645,6 +4825,7 @@ var init_teams2 = __esm({
4645
4825
  "src/cli/commands/teams.ts"() {
4646
4826
  "use strict";
4647
4827
  init_index();
4828
+ init_error_boundary();
4648
4829
  init_flags();
4649
4830
  init_formatters();
4650
4831
  init_ui();
@@ -4662,10 +4843,10 @@ var init_teams2 = __esm({
4662
4843
  },
4663
4844
  args: {
4664
4845
  ...OPTIONAL_COMPETITION_FLAG,
4665
- "team-type": { type: "string", description: "Team type filter" },
4846
+ "team-type": { type: "string", description: "Team type filter (e.g. CLUB, REPRESENTATIVE)" },
4666
4847
  ...OUTPUT_FLAGS
4667
4848
  },
4668
- async run({ args }) {
4849
+ run: withErrorBoundary(async ({ args }) => {
4669
4850
  const competition = validateOptionalCompetition(args.competition);
4670
4851
  const format = validateFormat(args.format);
4671
4852
  const result = await withSpinner(
@@ -4677,8 +4858,8 @@ var init_teams2 = __esm({
4677
4858
  }
4678
4859
  const data = result.data;
4679
4860
  if (data.length === 0 && args["team-type"]) {
4680
- console.error(
4681
- `No teams found for team type "${args["team-type"]}". Try running without --team-type to see available teams.`
4861
+ throw new Error(
4862
+ `No teams found for team type "${args["team-type"]}". Known team types include: CLUB, REPRESENTATIVE. Try running without --team-type to see all teams.`
4682
4863
  );
4683
4864
  }
4684
4865
  showSummary(`Loaded ${data.length} teams`);
@@ -4690,7 +4871,7 @@ var init_teams2 = __esm({
4690
4871
  columns: DEFAULT_COLUMNS7
4691
4872
  };
4692
4873
  console.log(formatOutput(data, formatOptions));
4693
- }
4874
+ })
4694
4875
  });
4695
4876
  }
4696
4877
  });
@@ -4704,14 +4885,19 @@ import { defineCommand as defineCommand8 } from "citty";
4704
4885
  function flattenEntries(data) {
4705
4886
  return data.map((entry) => {
4706
4887
  const { stats, ...rest } = entry;
4707
- return { ...rest, ...stats };
4888
+ const normalised = {};
4889
+ for (const [key, value] of Object.entries(stats)) {
4890
+ normalised[AFL_TABLES_KEY_MAP[key] ?? key] = value;
4891
+ }
4892
+ return { ...rest, ...normalised };
4708
4893
  });
4709
4894
  }
4710
- var DEFAULT_COLUMNS8, teamStatsCommand;
4895
+ var DEFAULT_COLUMNS8, AFL_TABLES_KEY_MAP, teamStatsCommand;
4711
4896
  var init_team_stats2 = __esm({
4712
4897
  "src/cli/commands/team-stats.ts"() {
4713
4898
  "use strict";
4714
4899
  init_index();
4900
+ init_error_boundary();
4715
4901
  init_flags();
4716
4902
  init_formatters();
4717
4903
  init_ui();
@@ -4728,6 +4914,53 @@ var init_team_stats2 = __esm({
4728
4914
  { key: "T", label: "T", maxWidth: 6 },
4729
4915
  { key: "I50", label: "I50", maxWidth: 6 }
4730
4916
  ];
4917
+ AFL_TABLES_KEY_MAP = {
4918
+ KI_for: "K",
4919
+ MK_for: "M",
4920
+ HB_for: "HB",
4921
+ DI_for: "D",
4922
+ GL_for: "G",
4923
+ BH_for: "B",
4924
+ HO_for: "HO",
4925
+ TK_for: "T",
4926
+ RB_for: "RB",
4927
+ IF_for: "IF",
4928
+ CL_for: "CL",
4929
+ CG_for: "CG",
4930
+ FF_for: "FF",
4931
+ BR_for: "BR",
4932
+ CP_for: "CP",
4933
+ UP_for: "UP",
4934
+ CM_for: "CM",
4935
+ MI_for: "MI",
4936
+ "1%_for": "1%",
4937
+ BO_for: "BO",
4938
+ GA_for: "GA",
4939
+ I50_for: "I50",
4940
+ // "against" variants
4941
+ KI_against: "K_against",
4942
+ MK_against: "M_against",
4943
+ HB_against: "HB_against",
4944
+ DI_against: "D_against",
4945
+ GL_against: "G_against",
4946
+ BH_against: "B_against",
4947
+ HO_against: "HO_against",
4948
+ TK_against: "T_against",
4949
+ RB_against: "RB_against",
4950
+ IF_against: "IF_against",
4951
+ CL_against: "CL_against",
4952
+ CG_against: "CG_against",
4953
+ FF_against: "FF_against",
4954
+ BR_against: "BR_against",
4955
+ CP_against: "CP_against",
4956
+ UP_against: "UP_against",
4957
+ CM_against: "CM_against",
4958
+ MI_against: "MI_against",
4959
+ "1%_against": "1%_against",
4960
+ BO_against: "BO_against",
4961
+ GA_against: "GA_against",
4962
+ I50_against: "I50_against"
4963
+ };
4731
4964
  teamStatsCommand = defineCommand8({
4732
4965
  meta: {
4733
4966
  name: "team-stats",
@@ -4743,11 +4976,11 @@ var init_team_stats2 = __esm({
4743
4976
  summary: { type: "string", description: "Summary type: totals or averages", default: "totals" },
4744
4977
  ...OUTPUT_FLAGS
4745
4978
  },
4746
- async run({ args }) {
4979
+ run: withErrorBoundary(async ({ args }) => {
4747
4980
  const season = validateSeason(args.season);
4748
4981
  const source = validateSource(args.source);
4749
4982
  const format = validateFormat(args.format);
4750
- const summaryType = args.summary;
4983
+ const summaryType = validateSummary(args.summary);
4751
4984
  const result = await withSpinner(
4752
4985
  "Fetching team stats\u2026",
4753
4986
  () => fetchTeamStats({ source, season, summaryType })
@@ -4766,7 +4999,7 @@ var init_team_stats2 = __esm({
4766
4999
  columns: DEFAULT_COLUMNS8
4767
5000
  };
4768
5001
  console.log(formatOutput(flat, formatOptions));
4769
- }
5002
+ })
4770
5003
  });
4771
5004
  }
4772
5005
  });
@@ -4782,6 +5015,7 @@ var init_player_details2 = __esm({
4782
5015
  "src/cli/commands/player-details.ts"() {
4783
5016
  "use strict";
4784
5017
  init_index();
5018
+ init_error_boundary();
4785
5019
  init_flags();
4786
5020
  init_formatters();
4787
5021
  init_resolvers();
@@ -4812,7 +5046,7 @@ var init_player_details2 = __esm({
4812
5046
  ...COMPETITION_FLAG,
4813
5047
  ...OUTPUT_FLAGS
4814
5048
  },
4815
- async run({ args }) {
5049
+ run: withErrorBoundary(async ({ args }) => {
4816
5050
  const source = validateSource(args.source);
4817
5051
  const competition = validateCompetition(args.competition);
4818
5052
  const format = validateFormat(args.format);
@@ -4835,7 +5069,7 @@ var init_player_details2 = __esm({
4835
5069
  columns: DEFAULT_COLUMNS9
4836
5070
  };
4837
5071
  console.log(formatOutput(data, formatOptions));
4838
- }
5072
+ })
4839
5073
  });
4840
5074
  }
4841
5075
  });
@@ -4851,6 +5085,7 @@ var init_coaches_votes2 = __esm({
4851
5085
  "src/cli/commands/coaches-votes.ts"() {
4852
5086
  "use strict";
4853
5087
  init_index();
5088
+ init_error_boundary();
4854
5089
  init_flags();
4855
5090
  init_formatters();
4856
5091
  init_resolvers();
@@ -4876,7 +5111,7 @@ var init_coaches_votes2 = __esm({
4876
5111
  ...TEAM_FLAG,
4877
5112
  ...OUTPUT_FLAGS
4878
5113
  },
4879
- async run({ args }) {
5114
+ run: withErrorBoundary(async ({ args }) => {
4880
5115
  const season = validateSeason(args.season);
4881
5116
  const round = args.round ? validateRound(args.round) : void 0;
4882
5117
  const competition = validateCompetition(args.competition);
@@ -4901,7 +5136,7 @@ var init_coaches_votes2 = __esm({
4901
5136
  columns: DEFAULT_COLUMNS10
4902
5137
  };
4903
5138
  console.log(formatOutput(data, formatOptions));
4904
- }
5139
+ })
4905
5140
  });
4906
5141
  }
4907
5142
  });
@@ -4909,37 +5144,34 @@ var init_coaches_votes2 = __esm({
4909
5144
  // src/cli.ts
4910
5145
  import { defineCommand as defineCommand11, runMain } from "citty";
4911
5146
 
4912
- // src/cli/error-boundary.ts
4913
- init_errors();
4914
- import pc from "picocolors";
4915
- function formatError(error) {
4916
- if (error instanceof ValidationError && error.issues) {
4917
- const issueLines = error.issues.map((i) => ` ${pc.yellow(i.path)}: ${i.message}`);
4918
- return `${pc.red("Validation error:")}
4919
- ${issueLines.join("\n")}`;
4920
- }
4921
- if (error instanceof AflApiError) {
4922
- const status = error.statusCode ? ` (HTTP ${error.statusCode})` : "";
4923
- return `${pc.red("AFL API error:")} ${error.message}${status}`;
4924
- }
4925
- if (error instanceof ScrapeError) {
4926
- const source = error.source ? ` [${error.source}]` : "";
4927
- return `${pc.red("Scrape error:")} ${error.message}${source}`;
4928
- }
4929
- if (error instanceof UnsupportedSourceError) {
4930
- return `${pc.red("Unsupported source:")} ${error.message}`;
4931
- }
4932
- if (error instanceof Error) {
4933
- return `${pc.red("Error:")} ${error.message}`;
5147
+ // src/cli/alias-resolution.ts
5148
+ var SHORT_TO_LONG = {
5149
+ "-s": "--season",
5150
+ "-r": "--round",
5151
+ "-c": "--competition",
5152
+ "-j": "--json",
5153
+ "-t": "--team",
5154
+ "-p": "--player"
5155
+ };
5156
+ function resolveAliases() {
5157
+ for (let i = 0; i < process.argv.length; i++) {
5158
+ const arg = process.argv[i];
5159
+ if (arg != null) {
5160
+ const long = SHORT_TO_LONG[arg];
5161
+ if (long) {
5162
+ process.argv[i] = long;
5163
+ }
5164
+ }
4934
5165
  }
4935
- return `${pc.red("Error:")} ${String(error)}`;
4936
5166
  }
4937
5167
 
4938
5168
  // src/cli.ts
5169
+ init_error_boundary();
5170
+ resolveAliases();
4939
5171
  var main = defineCommand11({
4940
5172
  meta: {
4941
5173
  name: "fitzroy",
4942
- version: "1.2.0",
5174
+ version: "1.3.1",
4943
5175
  description: "CLI for fetching AFL data \u2014 match results, player stats, fixtures, ladders, and more"
4944
5176
  },
4945
5177
  subCommands: {