@gotza02/sequential-thinking 2026.2.39 → 2026.2.41

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 (38) hide show
  1. package/README.md +7 -4
  2. package/SYSTEM_INSTRUCTION.md +25 -50
  3. package/dist/tools/sports/core/base.d.ts +169 -0
  4. package/dist/tools/sports/core/base.js +289 -0
  5. package/dist/tools/sports/core/cache.d.ts +106 -0
  6. package/dist/tools/sports/core/cache.js +305 -0
  7. package/dist/tools/sports/core/constants.d.ts +179 -0
  8. package/dist/tools/sports/core/constants.js +149 -0
  9. package/dist/tools/sports/core/types.d.ts +379 -0
  10. package/dist/tools/sports/core/types.js +5 -0
  11. package/dist/tools/sports/index.d.ts +34 -0
  12. package/dist/tools/sports/index.js +50 -0
  13. package/dist/tools/sports/providers/api.d.ts +73 -0
  14. package/dist/tools/sports/providers/api.js +517 -0
  15. package/dist/tools/sports/providers/scraper.d.ts +66 -0
  16. package/dist/tools/sports/providers/scraper.js +186 -0
  17. package/dist/tools/sports/providers/search.d.ts +54 -0
  18. package/dist/tools/sports/providers/search.js +224 -0
  19. package/dist/tools/sports/tools/betting.d.ts +6 -0
  20. package/dist/tools/sports/tools/betting.js +251 -0
  21. package/dist/tools/sports/tools/league.d.ts +11 -0
  22. package/dist/tools/sports/tools/league.js +12 -0
  23. package/dist/tools/sports/tools/live.d.ts +9 -0
  24. package/dist/tools/sports/tools/live.js +235 -0
  25. package/dist/tools/sports/tools/match.d.ts +6 -0
  26. package/dist/tools/sports/tools/match.js +323 -0
  27. package/dist/tools/sports/tools/player.d.ts +6 -0
  28. package/dist/tools/sports/tools/player.js +152 -0
  29. package/dist/tools/sports/tools/team.d.ts +6 -0
  30. package/dist/tools/sports/tools/team.js +370 -0
  31. package/dist/tools/sports/utils/calculator.d.ts +69 -0
  32. package/dist/tools/sports/utils/calculator.js +156 -0
  33. package/dist/tools/sports/utils/formatter.d.ts +57 -0
  34. package/dist/tools/sports/utils/formatter.js +206 -0
  35. package/dist/tools/sports.d.ts +7 -0
  36. package/dist/tools/sports.js +27 -6
  37. package/package.json +1 -1
  38. package/system_instruction.md +155 -0
@@ -0,0 +1,186 @@
1
+ /**
2
+ * SPORTS MODULE SCRAPER PROVIDER
3
+ * Web scraping provider for sports data from various websites
4
+ */
5
+ import { ScraperProviderBase } from '../core/base.js';
6
+ import { SCRAPER_CONFIG } from '../core/constants.js';
7
+ import { fetchWithRetry } from '../../../utils.js';
8
+ import { JSDOM } from 'jsdom';
9
+ import { Readability } from '@mozilla/readability';
10
+ import TurndownService from 'turndown';
11
+ /**
12
+ * Web Scraper Provider for sports data
13
+ */
14
+ export class ScraperProvider extends ScraperProviderBase {
15
+ turndown;
16
+ constructor() {
17
+ super();
18
+ this.turndown = new TurndownService({
19
+ headingStyle: 'atx',
20
+ codeBlockStyle: 'fenced',
21
+ });
22
+ }
23
+ /**
24
+ * Check if a URL is from a priority domain
25
+ */
26
+ isPriorityDomain(url) {
27
+ return SCRAPER_CONFIG.PRIORITY_DOMAINS.some(domain => url.includes(domain));
28
+ }
29
+ /**
30
+ * Scrape a webpage and return structured data
31
+ * Overrides the base class method with a specific implementation
32
+ */
33
+ async scrape(url, extractor) {
34
+ try {
35
+ const response = await fetchWithRetry(url, {
36
+ headers: {
37
+ 'User-Agent': SCRAPER_CONFIG.USER_AGENT,
38
+ },
39
+ });
40
+ const html = await response.text();
41
+ if (extractor) {
42
+ const data = extractor(html);
43
+ return {
44
+ success: true,
45
+ data,
46
+ provider: 'scraper',
47
+ };
48
+ }
49
+ // Default extraction: extract article content
50
+ const doc = new JSDOM(html, { url });
51
+ const reader = new Readability(doc.window.document);
52
+ const article = reader.parse();
53
+ if (!article) {
54
+ return {
55
+ success: false,
56
+ error: 'Could not parse article content',
57
+ };
58
+ }
59
+ let content = this.turndown.turndown(article.content || '');
60
+ // Truncate if too long
61
+ if (content.length > SCRAPER_CONFIG.MAX_CONTENT_LENGTH) {
62
+ content = content.substring(0, SCRAPER_CONFIG.MAX_CONTENT_LENGTH) + '\n...(truncated)';
63
+ }
64
+ return {
65
+ success: true,
66
+ data: `Title: ${article.title}\n\n${content}`,
67
+ provider: 'scraper',
68
+ };
69
+ }
70
+ catch (error) {
71
+ return {
72
+ success: false,
73
+ error: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+ }
77
+ /**
78
+ * Public method to scrape and get markdown content
79
+ */
80
+ async scrapeToMarkdown(url) {
81
+ return this.scrape(url);
82
+ }
83
+ /**
84
+ * Extract match data from a webpage
85
+ */
86
+ async getMatch(matchId) {
87
+ // For scraper, matchId should be a URL
88
+ return {
89
+ success: false,
90
+ error: 'Direct match scraping not implemented. Use URL-based scraping.',
91
+ };
92
+ }
93
+ /**
94
+ * Get live matches from a scoring website
95
+ */
96
+ async getLiveMatches(leagueId) {
97
+ // This would require scraping live score websites
98
+ return {
99
+ success: false,
100
+ error: 'Live score scraping not implemented. Use API providers.',
101
+ };
102
+ }
103
+ /**
104
+ * Get standings from a webpage
105
+ */
106
+ async getStandings(leagueId) {
107
+ return {
108
+ success: false,
109
+ error: 'Standings scraping not implemented. Use API providers.',
110
+ };
111
+ }
112
+ /**
113
+ * Get team information from a webpage
114
+ */
115
+ async getTeam(teamId) {
116
+ // teamId should be a URL
117
+ return {
118
+ success: false,
119
+ error: 'Team scraping not implemented. Use URL-based scraping.',
120
+ };
121
+ }
122
+ /**
123
+ * Get player information from a webpage
124
+ */
125
+ async getPlayer(playerId) {
126
+ return {
127
+ success: false,
128
+ error: 'Player scraping not implemented. Use API providers.',
129
+ };
130
+ }
131
+ /**
132
+ * Search for teams via web search
133
+ */
134
+ async searchTeams(query) {
135
+ return {
136
+ success: false,
137
+ error: 'Team search via scraping not implemented.',
138
+ };
139
+ }
140
+ /**
141
+ * Search for players via web search
142
+ */
143
+ async searchPlayers(query) {
144
+ return {
145
+ success: false,
146
+ error: 'Player search via scraping not implemented.',
147
+ };
148
+ }
149
+ /**
150
+ * Scrape a URL with timeout protection
151
+ */
152
+ async scrapeWithTimeout(url, timeout = SCRAPER_CONFIG.TIMEOUT) {
153
+ const scrapePromise = this.scrapeToMarkdown(url);
154
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Scrape timeout')), timeout));
155
+ try {
156
+ return await Promise.race([scrapePromise, timeoutPromise]);
157
+ }
158
+ catch (error) {
159
+ return {
160
+ success: false,
161
+ error: error instanceof Error ? error.message : String(error),
162
+ };
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Scrape match preview/article content
168
+ */
169
+ export async function scrapeMatchContent(url) {
170
+ const scraper = new ScraperProvider();
171
+ return scraper.scrapeToMarkdown(url);
172
+ }
173
+ /**
174
+ * Find best URL for match data from search results
175
+ */
176
+ export function findBestMatchUrl(urls) {
177
+ const priorityDomains = SCRAPER_CONFIG.PRIORITY_DOMAINS;
178
+ // Find first URL from priority domain
179
+ for (const url of urls) {
180
+ if (priorityDomains.some(domain => url.includes(domain))) {
181
+ return url;
182
+ }
183
+ }
184
+ // Return first URL if no priority match
185
+ return urls[0] || null;
186
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * SPORTS MODULE SEARCH PROVIDER
3
+ * Abstraction layer for web search providers (Brave, Exa, Google)
4
+ */
5
+ import { SearchQuery, QueryType } from '../core/types.js';
6
+ /**
7
+ * Search result
8
+ */
9
+ export interface SearchResult {
10
+ title: string;
11
+ url: string;
12
+ snippet: string;
13
+ publishedDate?: string;
14
+ score?: number;
15
+ }
16
+ /**
17
+ * Unified Search Provider with fallback
18
+ */
19
+ export declare class SearchProvider {
20
+ private providers;
21
+ constructor();
22
+ /**
23
+ * Get available providers
24
+ */
25
+ getAvailableProviders(): string[];
26
+ /**
27
+ * Search with automatic fallback
28
+ */
29
+ search(query: string, preferredProvider?: string, count?: number): Promise<SearchResult[]>;
30
+ /**
31
+ * Perform multiple searches in parallel
32
+ */
33
+ searchMultiple(queries: SearchQuery[], count?: number): Promise<Map<QueryType, SearchResult[]>>;
34
+ /**
35
+ * Build football match search queries
36
+ */
37
+ static buildMatchQueries(homeTeam: string, awayTeam: string, league?: string, context?: string): SearchQuery[];
38
+ /**
39
+ * Extract URLs from search results
40
+ */
41
+ static extractURLs(results: SearchResult[]): string[];
42
+ /**
43
+ * Format search results as markdown
44
+ */
45
+ static formatAsMarkdown(results: SearchResult[], title: string): string;
46
+ }
47
+ /**
48
+ * Get or create the global search provider
49
+ */
50
+ export declare function getSearchProvider(): SearchProvider;
51
+ /**
52
+ * Reset the global search provider
53
+ */
54
+ export declare function resetSearchProvider(): void;
@@ -0,0 +1,224 @@
1
+ /**
2
+ * SPORTS MODULE SEARCH PROVIDER
3
+ * Abstraction layer for web search providers (Brave, Exa, Google)
4
+ */
5
+ import { QUERY_TYPES } from '../core/constants.js';
6
+ import { fetchWithRetry } from '../../../utils.js';
7
+ import { logger } from '../../../utils.js';
8
+ /**
9
+ * Brave Search Provider
10
+ */
11
+ class BraveSearchProvider {
12
+ name = 'brave';
13
+ isAvailable() {
14
+ return !!process.env.BRAVE_API_KEY;
15
+ }
16
+ async search(query, count = 5) {
17
+ const response = await fetchWithRetry(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`, {
18
+ headers: {
19
+ 'X-Subscription-Token': process.env.BRAVE_API_KEY,
20
+ 'Accept': 'application/json',
21
+ },
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`Brave API error: ${response.status}`);
25
+ }
26
+ const data = await response.json();
27
+ const results = data.web?.results || [];
28
+ return results.map((r) => ({
29
+ title: r.title,
30
+ url: r.url,
31
+ snippet: r.description,
32
+ }));
33
+ }
34
+ }
35
+ /**
36
+ * Exa Search Provider
37
+ */
38
+ class ExaSearchProvider {
39
+ name = 'exa';
40
+ isAvailable() {
41
+ return !!process.env.EXA_API_KEY;
42
+ }
43
+ async search(query, count = 5) {
44
+ const response = await fetchWithRetry('https://api.exa.ai/search', {
45
+ method: 'POST',
46
+ headers: {
47
+ 'x-api-key': process.env.EXA_API_KEY,
48
+ 'Content-Type': 'application/json',
49
+ },
50
+ body: JSON.stringify({ query, numResults: count }),
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`Exa API error: ${response.status}`);
54
+ }
55
+ const data = await response.json();
56
+ const results = data.results || [];
57
+ return results.map((r) => ({
58
+ title: r.title,
59
+ url: r.url,
60
+ snippet: r.text || r.title,
61
+ publishedDate: r.publishedDate,
62
+ }));
63
+ }
64
+ }
65
+ /**
66
+ * Google Custom Search Provider
67
+ */
68
+ class GoogleSearchProvider {
69
+ name = 'google';
70
+ isAvailable() {
71
+ return !!(process.env.GOOGLE_SEARCH_API_KEY && process.env.GOOGLE_SEARCH_CX);
72
+ }
73
+ async search(query, count = 5) {
74
+ const response = await fetchWithRetry(`https://www.googleapis.com/customsearch/v1?key=${process.env.GOOGLE_SEARCH_API_KEY}&cx=${process.env.GOOGLE_SEARCH_CX}&q=${encodeURIComponent(query)}&num=${count}`);
75
+ if (!response.ok) {
76
+ throw new Error(`Google API error: ${response.status}`);
77
+ }
78
+ const data = await response.json();
79
+ const items = data.items || [];
80
+ return items.map((item) => ({
81
+ title: item.title,
82
+ url: item.link,
83
+ snippet: item.snippet,
84
+ }));
85
+ }
86
+ }
87
+ /**
88
+ * Unified Search Provider with fallback
89
+ */
90
+ export class SearchProvider {
91
+ providers = [];
92
+ constructor() {
93
+ // Initialize available providers
94
+ const brave = new BraveSearchProvider();
95
+ if (brave.isAvailable()) {
96
+ this.providers.push(brave);
97
+ logger.info('Brave search provider initialized');
98
+ }
99
+ const exa = new ExaSearchProvider();
100
+ if (exa.isAvailable()) {
101
+ this.providers.push(exa);
102
+ logger.info('Exa search provider initialized');
103
+ }
104
+ const google = new GoogleSearchProvider();
105
+ if (google.isAvailable()) {
106
+ this.providers.push(google);
107
+ logger.info('Google search provider initialized');
108
+ }
109
+ if (this.providers.length === 0) {
110
+ logger.warn('No search providers available. Set BRAVE_API_KEY, EXA_API_KEY, or GOOGLE_SEARCH_API_KEY.');
111
+ }
112
+ }
113
+ /**
114
+ * Get available providers
115
+ */
116
+ getAvailableProviders() {
117
+ return this.providers.map(p => p.name);
118
+ }
119
+ /**
120
+ * Search with automatic fallback
121
+ */
122
+ async search(query, preferredProvider, count = 5) {
123
+ // Sort providers: preferred first, then others
124
+ let providers = this.providers;
125
+ if (preferredProvider) {
126
+ const preferred = this.providers.find(p => p.name === preferredProvider);
127
+ providers = preferred
128
+ ? [preferred, ...this.providers.filter(p => p.name !== preferredProvider)]
129
+ : this.providers;
130
+ }
131
+ const errors = [];
132
+ for (const provider of providers) {
133
+ try {
134
+ const results = await provider.search(query, count);
135
+ logger.debug(`${provider.name} search returned ${results.length} results`);
136
+ return results;
137
+ }
138
+ catch (error) {
139
+ const errorMsg = error instanceof Error ? error.message : String(error);
140
+ errors.push(`${provider.name}: ${errorMsg}`);
141
+ logger.warn(`${provider.name} search failed: ${errorMsg}`);
142
+ }
143
+ }
144
+ throw new Error(`All search providers failed:\n${errors.join('\n')}`);
145
+ }
146
+ /**
147
+ * Perform multiple searches in parallel
148
+ */
149
+ async searchMultiple(queries, count = 4) {
150
+ const results = new Map();
151
+ // Sort by priority (higher first)
152
+ const sortedQueries = [...queries].sort((a, b) => b.priority - a.priority);
153
+ // Search in parallel
154
+ const searchPromises = sortedQueries.map(async (query) => {
155
+ try {
156
+ const searchResults = await this.search(query.query, undefined, count);
157
+ return { type: query.type, results: searchResults };
158
+ }
159
+ catch (error) {
160
+ logger.error(`Search failed for ${query.type}: ${error}`);
161
+ return { type: query.type, results: [] };
162
+ }
163
+ });
164
+ const allSearchResults = await Promise.all(searchPromises);
165
+ for (const { type, results: searchResults } of allSearchResults) {
166
+ results.set(type, searchResults);
167
+ }
168
+ return results;
169
+ }
170
+ /**
171
+ * Build football match search queries
172
+ */
173
+ static buildMatchQueries(homeTeam, awayTeam, league, context) {
174
+ const leagueStr = league ? `${league} ` : '';
175
+ const baseQuery = `${leagueStr}${homeTeam} vs ${awayTeam}`;
176
+ const today = new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
177
+ const dateQuery = ` ${today}`;
178
+ return [
179
+ { type: QUERY_TYPES.GENERAL, query: `${baseQuery} match prediction stats${dateQuery}`, priority: 10 },
180
+ { type: QUERY_TYPES.H2H, query: `${baseQuery} head to head history`, priority: 8 },
181
+ { type: QUERY_TYPES.FORM, query: `${homeTeam} ${awayTeam} recent form last 5 matches${dateQuery}`, priority: 7 },
182
+ { type: 'news', query: `${baseQuery} team news injuries lineups${dateQuery}`, priority: 9 },
183
+ { type: QUERY_TYPES.STATS, query: `${baseQuery} xG expected goals stats${dateQuery}`, priority: 6 },
184
+ { type: QUERY_TYPES.ODDS, query: `${baseQuery} odds movement dropping trends${dateQuery}`, priority: 8 },
185
+ { type: QUERY_TYPES.FATIGUE, query: `${homeTeam} ${awayTeam} days rest fixture congestion${dateQuery}`, priority: 5 },
186
+ { type: QUERY_TYPES.SETPIECES, query: `${baseQuery} set pieces corners aerial duels${dateQuery}`, priority: 5 },
187
+ ];
188
+ }
189
+ /**
190
+ * Extract URLs from search results
191
+ */
192
+ static extractURLs(results) {
193
+ return results.map(r => r.url);
194
+ }
195
+ /**
196
+ * Format search results as markdown
197
+ */
198
+ static formatAsMarkdown(results, title) {
199
+ if (results.length === 0) {
200
+ return `### ${title}\nNo results found.\n`;
201
+ }
202
+ const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
203
+ return `### ${title}\n${items}\n`;
204
+ }
205
+ }
206
+ /**
207
+ * Singleton search provider instance
208
+ */
209
+ let globalSearchProvider = null;
210
+ /**
211
+ * Get or create the global search provider
212
+ */
213
+ export function getSearchProvider() {
214
+ if (!globalSearchProvider) {
215
+ globalSearchProvider = new SearchProvider();
216
+ }
217
+ return globalSearchProvider;
218
+ }
219
+ /**
220
+ * Reset the global search provider
221
+ */
222
+ export function resetSearchProvider() {
223
+ globalSearchProvider = null;
224
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * SPORTS MODULE TOOLS - Betting Tools
3
+ * Tools for betting intelligence and odds comparison
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ export declare function registerBettingTools(server: McpServer): void;