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.
- package/dist/bin/bbdata.js +128 -16
- package/dist/bin/bbdata.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +122 -12
- package/dist/src/index.js.map +1 -1
- package/dist/templates/queries/hitter-batted-ball.ts +66 -0
- package/dist/templates/queries/hitter-hot-cold-zones.ts +81 -0
- package/dist/templates/queries/hitter-vs-pitch-type.ts +78 -0
- package/dist/templates/queries/index.ts +24 -0
- package/dist/templates/queries/leaderboard-comparison.ts +72 -0
- package/dist/templates/queries/leaderboard-custom.ts +90 -0
- package/dist/templates/queries/matchup-pitcher-vs-hitter.ts +81 -0
- package/dist/templates/queries/matchup-situational.ts +68 -0
- package/dist/templates/queries/pitcher-arsenal.ts +89 -0
- package/dist/templates/queries/pitcher-handedness-splits.ts +81 -0
- package/dist/templates/queries/pitcher-velocity-trend.ts +73 -0
- package/dist/templates/queries/registry.ts +73 -0
- package/dist/templates/queries/trend-rolling-average.ts +86 -0
- package/dist/templates/queries/trend-year-over-year.ts +73 -0
- package/dist/templates/reports/advance-lineup.hbs +29 -0
- package/dist/templates/reports/advance-sp.hbs +60 -0
- package/dist/templates/reports/college-hitter-draft.hbs +49 -0
- package/dist/templates/reports/college-pitcher-draft.hbs +48 -0
- package/dist/templates/reports/dev-progress.hbs +29 -0
- package/dist/templates/reports/draft-board-card.hbs +35 -0
- package/dist/templates/reports/hs-prospect.hbs +48 -0
- package/dist/templates/reports/partials/footer.hbs +7 -0
- package/dist/templates/reports/partials/header.hbs +12 -0
- package/dist/templates/reports/post-promotion.hbs +25 -0
- package/dist/templates/reports/pro-hitter-eval.hbs +65 -0
- package/dist/templates/reports/pro-pitcher-eval.hbs +69 -0
- package/dist/templates/reports/registry.ts +215 -0
- package/dist/templates/reports/relief-pitcher-quick.hbs +29 -0
- package/dist/templates/reports/trade-target-onepager.hbs +45 -0
- package/package.json +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -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: "
|
|
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)) {
|