import { SlashCommandBuilder } from 'discord.js'; import { BOOK_CHOICES, DATE_WINDOW_CHOICES, RESOLUTION_CHOICES, SPORT_CHOICES, STATUS_FILTER_CHOICES, } from './constants.js'; function addAnalyticsFilters(command, options = {}) { command.addStringOption((option) => option .setName('date_window') .setDescription('Filter analytics by time window.') .setRequired(false) .addChoices(...DATE_WINDOW_CHOICES) ); if (options.includeSport !== false) { command.addStringOption((option) => option .setName('sport') .setDescription('Filter by sport/league.') .setRequired(false) .addChoices(...SPORT_CHOICES) ); } if (options.includeBook) { command.addStringOption((option) => option .setName('book') .setDescription('Filter by sportsbook.') .setRequired(false) .addChoices(...BOOK_CHOICES) ); } if (options.includeStatus) { command.addStringOption((option) => option .setName('status') .setDescription('Filter by bet status.') .setRequired(false) .addChoices(...STATUS_FILTER_CHOICES) ); } return command; } const SPORTSBOOK_BOOK_CHOICES = [ { name: 'Circa', value: 'Circa' }, { name: 'FanDuel', value: 'FanDuel' }, { name: 'DraftKings', value: 'DraftKings' }, { name: 'BetMGM', value: 'BetMGM' }, { name: 'Caesars', value: 'Caesars' }, { name: 'Fanatics', value: 'Fanatics' }, { name: 'Bally Bet', value: 'Bally Bet' }, { name: 'Hard Rock Bet', value: 'Hard Rock Bet' }, ]; const DFS_BOOK_CHOICES = [ { name: 'Betr Picks', value: 'Betr Picks' }, { name: 'DraftKings Pick6', value: 'DraftKings Pick6' }, { name: 'PrizePicks', value: 'PrizePicks' }, { name: 'Underdog Fantasy', value: 'Underdog Fantasy' }, ]; const EXCHANGE_BOOK_CHOICES = [ { name: 'Kalshi', value: 'Kalshi' }, { name: 'Novig', value: 'Novig' }, { name: 'Polymarket', value: 'Polymarket' }, { name: 'ProphetX', value: 'ProphetX' }, ]; const MARKET_INTELLIGENCE_MARKET_CHOICES = [ { name: 'Home Runs', value: 'home_runs' }, { name: 'Hits', value: 'hits' }, { name: 'Total Bases', value: 'total_bases' }, { name: 'RBIs', value: 'rbis' }, { name: 'Runs', value: 'runs' }, { name: 'Steals', value: 'steals' }, { name: 'Hits + Runs + RBIs', value: 'hits_runs_rbis' }, { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts_generic' }, ]; function addMarketIntelligenceFilters(command, options = {}) { const bookChoices = options.bookChoices ?? SPORTSBOOK_BOOK_CHOICES; if (options.includeBook && options.bookRequired === true) { command.addStringOption((option) => option .setName('book') .setDescription('Optional book filter.') .setRequired(true) .addChoices(...bookChoices) ); } if (options.includePlayer && options.playerRequired === true) { command.addStringOption((option) => option .setName('player') .setDescription('Optional player filter.') .setRequired(true) ); } if (options.includeMarket !== false) { command.addStringOption((option) => option .setName('market') .setDescription('Optional market filter.') .setRequired(options.marketRequired === true) .addChoices(...MARKET_INTELLIGENCE_MARKET_CHOICES) ); } if (options.includeBook && options.bookRequired !== true) { command.addStringOption((option) => option .setName('book') .setDescription('Optional book filter.') .setRequired(false) .addChoices(...bookChoices) ); } if (options.includeTeam) { command.addStringOption((option) => option .setName('team') .setDescription('Optional team code filter, for example KCR or CHC.') .setRequired(false) ); } if (options.includePlayer && options.playerRequired !== true) { command.addStringOption((option) => option .setName('player') .setDescription('Optional player filter.') .setRequired(false) ); } if (options.includeMinEdge) { command.addNumberOption((option) => option .setName('min_edge') .setDescription('Optional minimum sharp edge threshold as a decimal, for example 0.04 for 4%.') .setRequired(false) ); } if (options.includeMinWidth) { command.addNumberOption((option) => option .setName('min_width') .setDescription('Optional minimum market width threshold as a decimal, for example 0.05 for 5%.') .setRequired(false) ); } if (options.includeLimit !== false) { command.addIntegerOption((option) => option .setName('limit') .setDescription('Optional result limit.') .setRequired(false) .setMinValue(1) .setMaxValue(25) ); } return command; } function addMatchupOptions(command, options = {}) { if (options.includePlayer) { command.addStringOption((option) => option .setName('player') .setDescription('Player name to look up.') .setRequired(true) ); } if (options.includePlayerType) { command.addStringOption((option) => option .setName('player_type') .setDescription('Optionally force hitter or pitcher context.') .setRequired(false) .addChoices( { name: 'Auto', value: 'auto' }, { name: 'Hitter', value: 'hitter' }, { name: 'Pitcher', value: 'pitcher' } ) ); } if (options.includeTeam) { command.addStringOption((option) => option .setName('team') .setDescription('Optional team filter, for example CHC or KCR.') .setRequired(false) ); } if (options.includeDate !== false) { command.addStringOption((option) => option .setName('date') .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') .setRequired(false) ); } if (options.includeLimit !== false) { command.addIntegerOption((option) => option .setName('limit') .setDescription('Optional result limit.') .setRequired(false) .setMinValue(1) .setMaxValue(15) ); } return command; } const BASEBALL_CHART_BOOK_CHOICES = SPORTSBOOK_BOOK_CHOICES; const PITCHER_SUITE_WINDOW_CHOICES = [ { name: 'Last 5', value: 'last_5' }, { name: 'Last 10', value: 'last_10' }, { name: 'Season 2026', value: 'season_2026' }, { name: 'Career', value: 'career' }, ]; const PITCHER_SUITE_SPLIT_CHOICES = [ { name: 'Overall', value: 'overall' }, { name: 'Vs LHB', value: 'vs_lhb' }, { name: 'Vs RHB', value: 'vs_rhb' }, ]; const PITCHER_TREND_VIEW_CHOICES = [ { name: 'Velocity', value: 'velo' }, { name: 'Spin', value: 'spin' }, { name: 'Release', value: 'release' }, { name: 'Form', value: 'form' }, { name: 'Results', value: 'results' }, { name: 'Baseline', value: 'baseline' }, ]; const PITCHER_ARSENAL_VIEW_CHOICES = [ { name: 'Shape', value: 'shape' }, { name: 'Movement', value: 'movement' }, { name: 'Usage', value: 'usage' }, { name: 'Evolution', value: 'evolution' }, { name: 'Outcomes', value: 'outcomes' }, { name: 'Platoon', value: 'platoon' }, ]; const PITCHER_LOCATION_VIEW_CHOICES = [ { name: 'Heatmap', value: 'heatmap' }, { name: 'By Pitch', value: 'bypitch' }, { name: 'Two Strike', value: 'twostrike' }, { name: 'Chase', value: 'chase' }, { name: 'Damage', value: 'damage' }, { name: 'Miss', value: 'miss' }, ]; const PITCHER_APPROACH_VIEW_CHOICES = [ { name: 'Count Usage', value: 'count_usage' }, { name: 'Count Whiff', value: 'count_whiff' }, { name: 'Ahead / Behind', value: 'ahead_behind' }, { name: 'First Pitch', value: 'first_pitch' }, { name: 'Putaway', value: 'putaway' }, ]; const PITCHER_COMPARE_VIEW_CHOICES = [ { name: 'Current vs Career', value: 'current_vs_career' }, { name: 'Recent vs Baseline', value: 'recent_vs_baseline' }, { name: 'Year Over Year', value: 'year_over_year' }, { name: 'Risk Reward', value: 'risk_reward' }, ]; const PITCHER_COMPARE_TO_CHOICES = [ { name: 'Season 2026', value: 'season_2026' }, { name: 'Career', value: 'career' }, { name: 'Prior 5', value: 'prior_5' }, { name: 'Prior 10', value: 'prior_10' }, ]; const PITCHER_COUNT_BUCKET_CHOICES = [ { name: 'All', value: 'all' }, { name: 'First Pitch', value: 'first_pitch' }, { name: 'Ahead', value: 'ahead' }, { name: 'Behind', value: 'behind' }, { name: 'Putaway', value: 'putaway' }, { name: 'Two Strike', value: 'two_strike' }, ]; const DIRECT_ODDS_MARKET_CHOICES = [ { name: 'Home Runs', value: 'batter_home_runs' }, { name: 'Hits', value: 'batter_hits' }, { name: 'Total Bases', value: 'batter_total_bases' }, { name: 'RBIs', value: 'batter_rbis' }, { name: 'Runs', value: 'batter_runs_scored' }, { name: 'Steals', value: 'batter_stolen_bases' }, { name: 'Hits + Runs + RBIs', value: 'batter_hits_runs_rbis' }, { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts' }, ]; function addBaseballChartDateOption(command) { return command.addStringOption((option) => option .setName('date') .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') .setRequired(false) ); } function addBaseballChartWindowOption(command) { return command.addIntegerOption((option) => option .setName('window') .setDescription('Optional number of recent slate points to chart.') .setRequired(false) .setMinValue(3) .setMaxValue(14) ); } function addBaseballChartLimitOption(command) { return command.addIntegerOption((option) => option .setName('limit') .setDescription('Optional result limit.') .setRequired(false) .setMinValue(1) .setMaxValue(15) ); } function addBaseballChartBookOption(command) { return command.addStringOption((option) => option .setName('book') .setDescription('Optional sportsbook filter.') .setRequired(false) .addChoices(...BASEBALL_CHART_BOOK_CHOICES) ); } function addPitcherSuitePitcherOption(command) { return command.addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ); } function addPitcherSuiteViewOption(command, choices) { return command.addStringOption((option) => option .setName('view') .setDescription('Chart view to render.') .setRequired(true) .addChoices(...choices) ); } function addPitcherSuitePitchTypeOption(command) { return command.addStringOption((option) => option .setName('pitch_type') .setDescription('Optional pitch type filter, for example FF, SL, or CH.') .setRequired(false) ); } function addPitcherSuiteWindowOption(command) { return command.addStringOption((option) => option .setName('window') .setDescription('Optional time window.') .setRequired(false) .addChoices(...PITCHER_SUITE_WINDOW_CHOICES) ); } function addPitcherSuiteSplitOption(command) { return command.addStringOption((option) => option .setName('split') .setDescription('Optional batter-side split.') .setRequired(false) .addChoices(...PITCHER_SUITE_SPLIT_CHOICES) ); } function addPitcherSuiteCompareToOption(command) { return command.addStringOption((option) => option .setName('compare_to') .setDescription('Optional comparison baseline.') .setRequired(false) .addChoices(...PITCHER_COMPARE_TO_CHOICES) ); } function addPitcherSuiteCountBucketOption(command) { return command.addStringOption((option) => option .setName('count_bucket') .setDescription('Optional count bucket filter.') .setRequired(false) .addChoices(...PITCHER_COUNT_BUCKET_CHOICES) ); } export const commands = [ new SlashCommandBuilder() .setName('bet') .setDescription('Log a new bet using a private modal.') .addStringOption((option) => option .setName('book') .setDescription('Choose the sportsbook for this bet.') .setRequired(true) .addChoices(...BOOK_CHOICES) ) .addStringOption((option) => option .setName('sport') .setDescription('Choose the sport/league for this bet.') .setRequired(true) .addChoices(...SPORT_CHOICES) ), new SlashCommandBuilder() .setName('bulkadd') .setDescription('Log multiple bets privately for a single sportsbook and sport.') .addStringOption((option) => option .setName('book') .setDescription('Choose the sportsbook for this batch.') .setRequired(true) .addChoices(...BOOK_CHOICES) ) .addStringOption((option) => option .setName('sport') .setDescription('Choose the sport/league for this batch.') .setRequired(true) .addChoices(...SPORT_CHOICES) ), new SlashCommandBuilder() .setName('editbet') .setDescription('Edit one of your open bets.') .addIntegerOption((option) => option.setName('bet_id').setDescription('Your bet number to edit.').setRequired(true) ) .addStringOption((option) => option .setName('book') .setDescription('Update sportsbook.') .setRequired(false) .addChoices(...BOOK_CHOICES) ) .addStringOption((option) => option .setName('sport') .setDescription('Update sport/league.') .setRequired(false) .addChoices(...SPORT_CHOICES) ) .addStringOption((option) => option.setName('prop').setDescription('Update prop/bet text.').setRequired(false) ) .addStringOption((option) => option.setName('odds').setDescription('Update odds.').setRequired(false) ) .addNumberOption((option) => option.setName('stake').setDescription('Update stake amount.').setRequired(false) ), new SlashCommandBuilder() .setName('deletebet') .setDescription('Soft delete one of your bets so it no longer counts in stats.') .addIntegerOption((option) => option.setName('bet_id').setDescription('Your bet number to delete.').setRequired(true) ) .addStringOption((option) => option.setName('reason').setDescription('Optional reason for deletion.').setRequired(false) ), new SlashCommandBuilder() .setName('resolve') .setDescription('Resolve one of your tracked bets.') .addIntegerOption((option) => option.setName('bet_id').setDescription('Your bet number to resolve.').setRequired(true) ) .addStringOption((option) => option .setName('result') .setDescription('The final result for this bet.') .setRequired(true) .addChoices(...RESOLUTION_CHOICES) ), new SlashCommandBuilder() .setName('resolveall') .setDescription('Resolve multiple of your tracked bets with the same result.') .addStringOption((option) => option .setName('bet_ids') .setDescription('Comma-separated bet IDs, for example: 12, 13, 14') .setRequired(true) ) .addStringOption((option) => option .setName('result') .setDescription('The final result for all listed bets.') .setRequired(true) .addChoices(...RESOLUTION_CHOICES) ), new SlashCommandBuilder() .setName('bankroll') .setDescription('View or update your bankroll and unit settings.') .addNumberOption((option) => option.setName('starting_bankroll').setDescription('Set your starting bankroll.').setRequired(false) ) .addNumberOption((option) => option.setName('unit_size').setDescription('Set your unit size in dollars.').setRequired(false) ), addAnalyticsFilters( new SlashCommandBuilder() .setName('roi') .setDescription('Show your ROI, net profit, and cumulative profit chart.'), { includeBook: true } ), addAnalyticsFilters( new SlashCommandBuilder() .setName('bets') .setDescription('Show your total bets and browse your logged bets.'), { includeBook: true, includeStatus: true } ), addAnalyticsFilters( new SlashCommandBuilder() .setName('summary') .setDescription('Show your full betting summary and chart.'), { includeBook: true } ), addAnalyticsFilters( new SlashCommandBuilder() .setName('books') .setDescription('Show your ROI and win rate broken down by sportsbook.'), { includeSport: true } ), addAnalyticsFilters( new SlashCommandBuilder() .setName('sports') .setDescription('Show your ROI and win rate broken down by sport.'), { includeBook: true, includeSport: false } ), addAnalyticsFilters( new SlashCommandBuilder() .setName('export') .setDescription('Export your bets to CSV.'), { includeBook: true, includeStatus: true } ), new SlashCommandBuilder() .setName('commands') .setDescription('Post a public command reference embed.'), new SlashCommandBuilder() .setName('scanstatus') .setDescription('Show the market scanner status and latest run info.'), new SlashCommandBuilder() .setName('oddsquota') .setDescription('Show the latest Odds API quota usage headers.'), new SlashCommandBuilder() .setName('scanrun') .setDescription('Run the Circa market scanner manually.'), new SlashCommandBuilder() .setName('scanreport') .setDescription('Post the daily scan reports immediately.'), new SlashCommandBuilder() .setName('circatest') .setDescription('Run a Circa OCR diagnostic and preview parsed entries.'), new SlashCommandBuilder() .setName('circamarket') .setDescription('Show the latest Circa market in the current channel.') .addStringOption((option) => option .setName('market') .setDescription('Choose which Circa market to show.') .setRequired(true) .addChoices( { name: 'Home Runs', value: 'home_runs' }, { name: 'Hits', value: 'hits' }, { name: 'Total Bases', value: 'total_bases' }, { name: 'RBIs', value: 'rbis' }, { name: 'Runs', value: 'runs' }, { name: 'Steals', value: 'steals' }, { name: 'Hits + Runs + RBIs', value: 'hits_runs_rbis' }, { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts_generic' } ) ), new SlashCommandBuilder() .setName('circahr') .setDescription('Show the latest Circa Home Runs market in this channel.'), new SlashCommandBuilder() .setName('circahits') .setDescription('Show the latest Circa Hits market in this channel.'), new SlashCommandBuilder() .setName('circatb') .setDescription('Show the latest Circa Total Bases market in this channel.'), new SlashCommandBuilder() .setName('circarbis') .setDescription('Show the latest Circa RBIs market in this channel.'), new SlashCommandBuilder() .setName('circaruns') .setDescription('Show the latest Circa Runs market in this channel.'), new SlashCommandBuilder() .setName('circasb') .setDescription('Show the latest Circa Steals market in this channel.'), new SlashCommandBuilder() .setName('circahrri') .setDescription('Show the latest Circa Hits + Runs + RBIs market in this channel.'), new SlashCommandBuilder() .setName('circak') .setDescription('Show the latest Circa Pitcher Strikeouts market in this channel.'), new SlashCommandBuilder() .setName('hrodds') .setDescription('Show current home run odds for one player across books.') .addStringOption((option) => option .setName('player') .setDescription('Player name to look up, for example Aaron Judge.') .setRequired(true) ) .addStringOption((option) => option .setName('book') .setDescription('Optionally limit the result to one book. Defaults to all books including Circa.') .setRequired(false) .addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('hitodds') .setDescription('Show current hits odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('tbodds') .setDescription('Show current total bases odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('rbiodds') .setDescription('Show current RBI odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('runodds') .setDescription('Show current run-scored odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('sbodds') .setDescription('Show current stolen base odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('kodds') .setDescription('Show current pitcher strikeout odds for one player across books.') .addStringOption((option) => option.setName('player').setDescription('Pitcher name to look up.').setRequired(true)) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('dfsodds') .setDescription('Show current DFS or pick\'em prices for one player within the DFS lane.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('market').setDescription('Choose which DFS market to query.').setRequired(true).addChoices(...DIRECT_ODDS_MARKET_CHOICES) ) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one DFS source.').setRequired(false).addChoices(...DFS_BOOK_CHOICES) ), new SlashCommandBuilder() .setName('exchangeodds') .setDescription('Show current exchange prices for one player within the exchange lane.') .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) .addStringOption((option) => option.setName('market').setDescription('Choose which exchange market to query.').setRequired(true).addChoices(...DIRECT_ODDS_MARKET_CHOICES) ) .addStringOption((option) => option.setName('book').setDescription('Optionally limit the result to one exchange source.').setRequired(false).addChoices(...EXCHANGE_BOOK_CHOICES) ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('edgeboard') .setDescription('Show the best current edges versus the sharp book.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('playeredge') .setDescription('Show sharp comparisons across all supported markets for one player.'), { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('marketedge') .setDescription('Show the best current edges for one market.'), { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('widthboard') .setDescription('Show the widest current market splits across books.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('consensusvs') .setDescription('Compare one book to the current sharp market.'), { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('dfsedgeboard') .setDescription('Show the best current DFS or pick\'em edges within the DFS lane.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('dfsplayeredge') .setDescription('Show DFS lane comparisons across all supported markets for one player.'), { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('dfsmarketedge') .setDescription('Show the best current DFS lane edges for one market.'), { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('dfswidthboard') .setDescription('Show the widest current DFS lane market splits.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('dfsconsensusvs') .setDescription('Compare one DFS source to the current DFS lane consensus.'), { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('exchangeedgeboard') .setDescription('Show the best current exchange-lane edges.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('exchangeplayeredge') .setDescription('Show exchange-lane comparisons across all supported markets for one player.'), { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('exchangemarketedge') .setDescription('Show the best current exchange-lane edges for one market.'), { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('exchangewidthboard') .setDescription('Show the widest current exchange-lane market splits.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('exchangeconsensusvs') .setDescription('Compare one exchange source to the current exchange lane consensus.'), { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('steam') .setDescription('Show recent price movement anchored to the current sharp market.'), { includeMarket: true, includeBook: true, includeTeam: true, includePlayer: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('sharpboard') .setDescription('Show the best current sharp-vs-soft values.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('bookscoreboard') .setDescription('Rank books by sharp mispricing count and size.'), { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } ), addMarketIntelligenceFilters( new SlashCommandBuilder() .setName('markethealth') .setDescription('Show coverage and market quality by supported market.'), { includeMarket: true, includeLimit: true } ), addMatchupOptions( new SlashCommandBuilder() .setName('matchuphitters') .setDescription('Show the best hitter matchups from the hosted MLB artifact slate.'), { includeTeam: true, includeDate: true, includeLimit: true } ), addMatchupOptions( new SlashCommandBuilder() .setName('matchuppitchers') .setDescription('Show the best pitcher matchups from the hosted MLB artifact slate.'), { includeTeam: true, includeDate: true, includeLimit: true } ), addMatchupOptions( new SlashCommandBuilder() .setName('playercontext') .setDescription('Show matchup context for one hitter or pitcher.'), { includePlayer: true, includePlayerType: true, includeDate: true, includeLimit: false } ), addMatchupOptions( new SlashCommandBuilder() .setName('bestmatchups') .setDescription('Show a compact board of the best hitter matchups for the active slate.'), { includeTeam: true, includeDate: true, includeLimit: true } ), new SlashCommandBuilder() .setName('teambestmatchups') .setDescription('Show the best 3 hitter matchups for one team from the active slate.') .addStringOption((option) => option .setName('team') .setDescription('Team filter, for example CHC or KCR.') .setRequired(true) ) .addStringOption((option) => option .setName('date') .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') .setRequired(false) ) .addIntegerOption((option) => option .setName('limit') .setDescription('Optional result limit.') .setRequired(false) .setMinValue(1) .setMaxValue(15) ), new SlashCommandBuilder() .setName('matchuphealth') .setDescription('Show hosted artifact freshness and Cockroach fallback status for matchup data.'), addBaseballChartDateOption( addBaseballChartLimitOption( new SlashCommandBuilder() .setName('hrboardchart') .setDescription('Render a branded chart of the top home run targets on the active slate.') .addStringOption((option) => option .setName('team') .setDescription('Optional team filter, for example CHC or KCR.') .setRequired(false) ) ) ), addBaseballChartDateOption( addBaseballChartWindowOption( new SlashCommandBuilder() .setName('hrtrend') .setDescription('Render a rolling home run trend chart for one hitter.') .addStringOption((option) => option .setName('player') .setDescription('Hitter name to chart.') .setRequired(true) ) ) ), addBaseballChartDateOption( new SlashCommandBuilder() .setName('hrprofile') .setDescription('Render a home run profile radar for one hitter.') .addStringOption((option) => option .setName('player') .setDescription('Hitter name to chart.') .setRequired(true) ) ), addBaseballChartDateOption( addBaseballChartBookOption( addBaseballChartLimitOption( new SlashCommandBuilder() .setName('hrvaluechart') .setDescription('Render a price-versus-model chart for current home run targets.') .addStringOption((option) => option .setName('team') .setDescription('Optional team filter, for example CHC or KCR.') .setRequired(false) ) ) ) ), addBaseballChartDateOption( new SlashCommandBuilder() .setName('hrzone') .setDescription('Render a zone overlay card for one hitter against the probable pitcher.') .addStringOption((option) => option .setName('player') .setDescription('Hitter name to chart.') .setRequired(true) ) ), addBaseballChartDateOption( addBaseballChartWindowOption( new SlashCommandBuilder() .setName('ktrend') .setDescription('Render a rolling strikeout trend chart for one pitcher.') .addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ) ) ), addBaseballChartDateOption( addBaseballChartBookOption( new SlashCommandBuilder() .setName('kladder') .setDescription('Render a strikeout ladder chart for one pitcher.') .addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ) ) ), addBaseballChartDateOption( new SlashCommandBuilder() .setName('kprofile') .setDescription('Render a pitcher strikeout profile radar.') .addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ) ), addBaseballChartDateOption( new SlashCommandBuilder() .setName('kmatchup') .setDescription('Render a pitcher strikeout matchup card against the opposing lineup.') .addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ) ), addBaseballChartDateOption( new SlashCommandBuilder() .setName('kcount') .setDescription('Render a count leverage chart for one pitcher.') .addStringOption((option) => option .setName('pitcher') .setDescription('Pitcher name to chart.') .setRequired(true) ) ), addBaseballChartDateOption( addPitcherSuiteSplitOption( addPitcherSuiteCompareToOption( addPitcherSuiteWindowOption( addPitcherSuitePitchTypeOption( addPitcherSuiteViewOption( addPitcherSuitePitcherOption( new SlashCommandBuilder() .setName('pitchertrend') .setDescription('Render time-based pitcher analysis charts beyond the daily slate.') ), PITCHER_TREND_VIEW_CHOICES ) ) ) ) ) ), addBaseballChartDateOption( addPitcherSuiteSplitOption( addPitcherSuiteWindowOption( addPitcherSuitePitchTypeOption( addPitcherSuiteViewOption( addPitcherSuitePitcherOption( new SlashCommandBuilder() .setName('pitcherarsenal') .setDescription('Render arsenal, movement, usage, and pitch outcome views for one pitcher.') ), PITCHER_ARSENAL_VIEW_CHOICES ) ) ) ) ), addBaseballChartDateOption( addPitcherSuiteCountBucketOption( addPitcherSuiteSplitOption( addPitcherSuitePitchTypeOption( addPitcherSuiteViewOption( addPitcherSuitePitcherOption( new SlashCommandBuilder() .setName('pitcherlocation') .setDescription('Render location, zone, and attack-pattern charts for one pitcher.') ), PITCHER_LOCATION_VIEW_CHOICES ) ) ) ) ), addBaseballChartDateOption( addPitcherSuiteSplitOption( addPitcherSuiteWindowOption( addPitcherSuitePitchTypeOption( addPitcherSuiteViewOption( addPitcherSuitePitcherOption( new SlashCommandBuilder() .setName('pitcherapproach') .setDescription('Render count-state and game-plan charts for one pitcher.') ), PITCHER_APPROACH_VIEW_CHOICES ) ) ) ) ), addBaseballChartDateOption( addPitcherSuiteWindowOption( addPitcherSuiteViewOption( addPitcherSuitePitcherOption( new SlashCommandBuilder() .setName('pitchercompare') .setDescription('Render baseline, year-over-year, and risk/reward comparison charts for one pitcher.') ), PITCHER_COMPARE_VIEW_CHOICES ) ) ), new SlashCommandBuilder() .setName('alerts') .setDescription('Post the analyst alert-role embed to the welcome channel.'), new SlashCommandBuilder() .setName('welcome') .setDescription('Post the welcome embed and tag everyone.'), new SlashCommandBuilder() .setName('bulkdeletemessages') .setDescription('Bulk delete recent messages from the current channel.') .addIntegerOption((option) => option .setName('count') .setDescription('How many of the most recent messages to inspect and bulk delete.') .setRequired(true) .setMinValue(1) .setMaxValue(1000) ), ].map((command) => command.toJSON());