bbdata-cli 0.1.0 → 0.2.0

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.
Files changed (35) hide show
  1. package/dist/bin/bbdata.js +128 -16
  2. package/dist/bin/bbdata.js.map +1 -1
  3. package/dist/src/index.d.ts +3 -1
  4. package/dist/src/index.js +122 -12
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/templates/queries/hitter-batted-ball.ts +66 -0
  7. package/dist/templates/queries/hitter-hot-cold-zones.ts +81 -0
  8. package/dist/templates/queries/hitter-vs-pitch-type.ts +78 -0
  9. package/dist/templates/queries/index.ts +24 -0
  10. package/dist/templates/queries/leaderboard-comparison.ts +72 -0
  11. package/dist/templates/queries/leaderboard-custom.ts +90 -0
  12. package/dist/templates/queries/matchup-pitcher-vs-hitter.ts +81 -0
  13. package/dist/templates/queries/matchup-situational.ts +68 -0
  14. package/dist/templates/queries/pitcher-arsenal.ts +89 -0
  15. package/dist/templates/queries/pitcher-handedness-splits.ts +81 -0
  16. package/dist/templates/queries/pitcher-velocity-trend.ts +73 -0
  17. package/dist/templates/queries/registry.ts +73 -0
  18. package/dist/templates/queries/trend-rolling-average.ts +86 -0
  19. package/dist/templates/queries/trend-year-over-year.ts +73 -0
  20. package/dist/templates/reports/advance-lineup.hbs +29 -0
  21. package/dist/templates/reports/advance-sp.hbs +60 -0
  22. package/dist/templates/reports/college-hitter-draft.hbs +49 -0
  23. package/dist/templates/reports/college-pitcher-draft.hbs +48 -0
  24. package/dist/templates/reports/dev-progress.hbs +29 -0
  25. package/dist/templates/reports/draft-board-card.hbs +35 -0
  26. package/dist/templates/reports/hs-prospect.hbs +48 -0
  27. package/dist/templates/reports/partials/footer.hbs +7 -0
  28. package/dist/templates/reports/partials/header.hbs +12 -0
  29. package/dist/templates/reports/post-promotion.hbs +25 -0
  30. package/dist/templates/reports/pro-hitter-eval.hbs +65 -0
  31. package/dist/templates/reports/pro-pitcher-eval.hbs +69 -0
  32. package/dist/templates/reports/registry.ts +215 -0
  33. package/dist/templates/reports/relief-pitcher-quick.hbs +29 -0
  34. package/dist/templates/reports/trade-target-onepager.hbs +45 -0
  35. package/package.json +1 -1
@@ -17,6 +17,7 @@ interface QueryOptions {
17
17
  format?: OutputFormat;
18
18
  source?: string;
19
19
  cache?: boolean;
20
+ stdin?: boolean;
20
21
  }
21
22
  interface QueryResult {
22
23
  data: Record<string, unknown>[];
@@ -44,6 +45,7 @@ interface ReportOptions {
44
45
  audience?: Audience;
45
46
  format?: 'markdown' | 'json';
46
47
  validate?: boolean;
48
+ stdin?: boolean;
47
49
  }
48
50
  interface ReportResult {
49
51
  content: string;
@@ -207,7 +209,7 @@ type BbdataConfig = z.infer<typeof ConfigSchema>;
207
209
  declare function getConfig(): BbdataConfig;
208
210
  declare function setConfig(updates: Partial<BbdataConfig>): BbdataConfig;
209
211
 
210
- type DataSource = 'savant' | 'fangraphs' | 'mlb-stats-api' | 'baseball-reference';
212
+ type DataSource = 'savant' | 'fangraphs' | 'mlb-stats-api' | 'baseball-reference' | 'stdin';
211
213
  declare const PitchDataSchema: z.ZodObject<{
212
214
  pitcher_id: z.ZodString;
213
215
  pitcher_name: z.ZodString;
package/dist/src/index.js CHANGED
@@ -162,8 +162,8 @@ var SavantAdapter = class {
162
162
  }
163
163
  const params = new URLSearchParams({
164
164
  all: "true",
165
- type: "detail",
166
- ...query2.stat_type === "pitching" ? { player_type: "pitcher", pitchers_lookup: playerId ?? "" } : { player_type: "batter", batters_lookup: playerId ?? "" },
165
+ type: "details",
166
+ ...query2.stat_type === "pitching" ? { player_type: "pitcher", "pitchers_lookup[]": playerId ?? "" } : { player_type: "batter", "batters_lookup[]": playerId ?? "" },
167
167
  ...query2.start_date ? { game_date_gt: query2.start_date } : { game_date_gt: `${query2.season}-01-01` },
168
168
  ...query2.end_date ? { game_date_lt: query2.end_date } : { game_date_lt: `${query2.season}-12-31` }
169
169
  });
@@ -189,6 +189,9 @@ var SavantAdapter = class {
189
189
  cast: true,
190
190
  cast_date: false
191
191
  });
192
+ if (rawRows.length === 0 && csvText.trim().length > 50) {
193
+ log.debug("Savant returned headers but no data rows");
194
+ }
192
195
  const filteredRows = rawRows.filter(
193
196
  (row) => row.pitch_type && String(row.pitch_type).trim() !== ""
194
197
  );
@@ -228,6 +231,7 @@ var SavantAdapter = class {
228
231
 
229
232
  // src/adapters/fangraphs.ts
230
233
  var FG_LEADERS_URL = "https://www.fangraphs.com/api/leaders/major-league/data";
234
+ var stripHtml = (s) => s.replace(/<[^>]*>/g, "");
231
235
  var FanGraphsAdapter = class {
232
236
  source = "fangraphs";
233
237
  description = "FanGraphs \u2014 aggregated stats, leaderboards, WAR, wRC+, FIP";
@@ -256,7 +260,7 @@ var FanGraphsAdapter = class {
256
260
  const parsed = JSON.parse(data);
257
261
  const nameNorm = name.toLowerCase().trim();
258
262
  const players = parsed.data ?? [];
259
- const getName = (p) => String(p.PlayerName ?? p.Name ?? "").toLowerCase().trim();
263
+ const getName = (p) => stripHtml(String(p.PlayerName ?? p.Name ?? "")).toLowerCase().trim();
260
264
  let match = players.find((p) => getName(p) === nameNorm);
261
265
  if (!match) {
262
266
  const tokens = nameNorm.split(/\s+/);
@@ -272,8 +276,8 @@ var FanGraphsAdapter = class {
272
276
  return {
273
277
  mlbam_id: String(match.xMLBAMID ?? ""),
274
278
  fangraphs_id: String(match.playerid ?? ""),
275
- name: String(match.PlayerName ?? match.Name),
276
- team: String(match.Team ?? "")
279
+ name: stripHtml(String(match.PlayerName ?? match.Name)),
280
+ team: stripHtml(String(match.TeamNameAbb ?? match.TeamName ?? match.Team ?? ""))
277
281
  };
278
282
  } catch (error) {
279
283
  log.warn(`FanGraphs player resolution failed: ${error}`);
@@ -305,7 +309,7 @@ var FanGraphsAdapter = class {
305
309
  let filtered = rows;
306
310
  if (query2.player_name) {
307
311
  const nameNorm = query2.player_name.toLowerCase().trim();
308
- const getName = (r) => String(r.PlayerName ?? r.Name ?? "").toLowerCase().trim();
312
+ const getName = (r) => stripHtml(String(r.PlayerName ?? r.Name ?? "")).toLowerCase().trim();
309
313
  const exact = rows.filter((r) => getName(r) === nameNorm);
310
314
  if (exact.length > 0) {
311
315
  filtered = exact;
@@ -319,8 +323,8 @@ var FanGraphsAdapter = class {
319
323
  }
320
324
  const stats = filtered.map((row) => ({
321
325
  player_id: String(row.xMLBAMID ?? row.playerid ?? ""),
322
- player_name: String(row.PlayerName ?? row.Name ?? ""),
323
- team: String(row.Team ?? ""),
326
+ player_name: stripHtml(String(row.PlayerName ?? row.Name ?? "")),
327
+ team: stripHtml(String(row.TeamNameAbb ?? row.TeamName ?? row.Team ?? "")),
324
328
  season: query2.season,
325
329
  stat_type: query2.stat_type,
326
330
  stats: row
@@ -394,13 +398,88 @@ var BaseballReferenceAdapter = class {
394
398
  }
395
399
  };
396
400
 
401
+ // src/adapters/stdin.ts
402
+ var StdinAdapter = class {
403
+ source = "stdin";
404
+ description = "Local data from stdin (for sandboxed environments)";
405
+ data = [];
406
+ player = null;
407
+ loaded = false;
408
+ /**
409
+ * Load data from a pre-read stdin string.
410
+ * Called by the CLI before the adapter is used.
411
+ */
412
+ load(raw) {
413
+ try {
414
+ const parsed = JSON.parse(raw);
415
+ if (Array.isArray(parsed)) {
416
+ this.data = parsed;
417
+ } else if (parsed.data && Array.isArray(parsed.data)) {
418
+ this.data = parsed.data;
419
+ if (parsed.player) {
420
+ this.player = parsed.player;
421
+ }
422
+ } else {
423
+ throw new Error('Expected JSON array or { "data": [...] } object');
424
+ }
425
+ this.loaded = true;
426
+ log.info(`Stdin adapter loaded ${this.data.length} records`);
427
+ } catch (error) {
428
+ throw new Error(
429
+ `Failed to parse stdin data: ${error instanceof Error ? error.message : String(error)}`
430
+ );
431
+ }
432
+ }
433
+ supports(_query) {
434
+ return this.loaded && this.data.length > 0;
435
+ }
436
+ async fetch(query2) {
437
+ if (!this.loaded) {
438
+ throw new Error("Stdin adapter has no data \u2014 pipe JSON via stdin with --stdin flag");
439
+ }
440
+ return {
441
+ data: this.data,
442
+ source: "stdin",
443
+ cached: false,
444
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
445
+ meta: {
446
+ rowCount: this.data.length,
447
+ season: query2.season,
448
+ query: query2
449
+ }
450
+ };
451
+ }
452
+ async resolvePlayer(name) {
453
+ if (this.player && this.player.name.toLowerCase() === name.toLowerCase()) {
454
+ return this.player;
455
+ }
456
+ const firstRecord = this.data[0];
457
+ if (firstRecord) {
458
+ const id = firstRecord.pitcher_id ?? firstRecord.player_id ?? "";
459
+ const recordName = firstRecord.pitcher_name ?? firstRecord.player_name ?? name;
460
+ if (id) {
461
+ return {
462
+ mlbam_id: id,
463
+ name: recordName
464
+ };
465
+ }
466
+ }
467
+ return null;
468
+ }
469
+ };
470
+
397
471
  // src/adapters/index.ts
472
+ var stdinAdapter = new StdinAdapter();
398
473
  var adapters = {
399
474
  "mlb-stats-api": new MlbStatsApiAdapter(),
400
475
  "savant": new SavantAdapter(),
401
476
  "fangraphs": new FanGraphsAdapter(),
402
- "baseball-reference": new BaseballReferenceAdapter()
477
+ "baseball-reference": new BaseballReferenceAdapter(),
478
+ "stdin": stdinAdapter
403
479
  };
480
+ function getStdinAdapter() {
481
+ return stdinAdapter;
482
+ }
404
483
  function resolveAdapters(preferred) {
405
484
  return preferred.map((source) => adapters[source]).filter(Boolean);
406
485
  }
@@ -579,6 +658,20 @@ function getTemplatesDir() {
579
658
  return dir;
580
659
  }
581
660
 
661
+ // src/utils/stdin.ts
662
+ function readStdin() {
663
+ return new Promise((resolve, reject) => {
664
+ if (process.stdin.isTTY) {
665
+ reject(new Error(`--stdin flag requires piped input. Usage: echo '{"data":[...]}' | bbdata query ... --stdin`));
666
+ return;
667
+ }
668
+ const chunks = [];
669
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
670
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
671
+ process.stdin.on("error", reject);
672
+ });
673
+ }
674
+
582
675
  // src/templates/queries/registry.ts
583
676
  var templates = /* @__PURE__ */ new Map();
584
677
  function registerTemplate(template13) {
@@ -1179,11 +1272,12 @@ var template9 = {
1179
1272
  "bbdata query leaderboard-custom --stat ERA --min-ip 100 --top 10 --format table"
1180
1273
  ],
1181
1274
  buildQuery(params) {
1275
+ const pitchingStats = ["era", "fip", "xfip", "siera", "whip", "k/9", "bb/9", "hr/9", "ip", "w", "sv", "hld"];
1276
+ const isPitching = pitchingStats.includes((params.stat ?? "").toLowerCase());
1182
1277
  return {
1183
1278
  season: params.season ?? (/* @__PURE__ */ new Date()).getFullYear(),
1184
1279
  team: params.team,
1185
- stat_type: "batting",
1186
- // Will be overridden based on stat
1280
+ stat_type: isPitching ? "pitching" : "batting",
1187
1281
  min_pa: params.minPa,
1188
1282
  min_ip: params.minIp
1189
1283
  };
@@ -1429,6 +1523,12 @@ registerTemplate(template12);
1429
1523
 
1430
1524
  // src/commands/query.ts
1431
1525
  async function query(options) {
1526
+ if (options.stdin) {
1527
+ const raw = await readStdin();
1528
+ const adapter = getStdinAdapter();
1529
+ adapter.load(raw);
1530
+ options.source = "stdin";
1531
+ }
1432
1532
  const config = getConfig();
1433
1533
  const outputFormat = options.format ?? config.defaultFormat;
1434
1534
  const template13 = getTemplate(options.template);
@@ -1469,6 +1569,10 @@ ${available}`);
1469
1569
  });
1470
1570
  const rows = template13.transform(adapterResult.data, params);
1471
1571
  const columns = template13.columns(params);
1572
+ if (rows.length === 0) {
1573
+ log.debug(`${adapter.source} returned 0 rows. Trying next source...`);
1574
+ continue;
1575
+ }
1472
1576
  result = {
1473
1577
  rows,
1474
1578
  columns,
@@ -1763,6 +1867,11 @@ ${sections}
1763
1867
  `;
1764
1868
  }
1765
1869
  async function report(options) {
1870
+ if (options.stdin) {
1871
+ const raw = await readStdin();
1872
+ const adapter = getStdinAdapter();
1873
+ adapter.load(raw);
1874
+ }
1766
1875
  const config = getConfig();
1767
1876
  const audience = options.audience ?? config.defaultAudience;
1768
1877
  const template13 = getReportTemplate(options.template);
@@ -1782,7 +1891,8 @@ ${available}`);
1782
1891
  player: options.player,
1783
1892
  team: options.team,
1784
1893
  season,
1785
- format: "json"
1894
+ format: "json",
1895
+ ...options.stdin ? { source: "stdin" } : {}
1786
1896
  });
1787
1897
  dataResults[req.queryTemplate] = result.data;
1788
1898
  if (!dataSources.includes(result.meta.source)) {