@gotza02/sequential-thinking 10000.2.0 → 10000.2.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/tools/sports/core/base.d.ts +3 -2
- package/dist/tools/sports/core/base.js +12 -10
- package/dist/tools/sports/core/cache.d.ts +9 -0
- package/dist/tools/sports/core/cache.js +25 -3
- package/dist/tools/sports/core/types.d.ts +6 -2
- package/dist/tools/sports/providers/api.d.ts +4 -0
- package/dist/tools/sports/providers/api.js +110 -27
- package/dist/tools/sports/tools/betting.js +16 -16
- package/dist/tools/sports/tools/league.d.ts +2 -7
- package/dist/tools/sports/tools/league.js +198 -8
- package/dist/tools/sports/tools/live.js +80 -38
- package/dist/tools/sports/tools/match.js +239 -34
- package/package.json +1 -1
- package/CLAUDE.md +0 -231
|
@@ -1,12 +1,202 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPORTS MODULE TOOLS - League Tools
|
|
3
3
|
* Tools for league standings and statistics
|
|
4
|
-
*
|
|
5
|
-
* This file will contain:
|
|
6
|
-
* - get_league_standings
|
|
7
|
-
* - get_top_scorers
|
|
8
|
-
*
|
|
9
|
-
* Status: Placeholder for Phase 2 implementation
|
|
10
4
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { createAPIProvider } from '../providers/api.js';
|
|
7
|
+
import { getSearchProvider } from '../providers/search.js';
|
|
8
|
+
import { getGlobalCache, CacheService } from '../core/cache.js';
|
|
9
|
+
import { formatStandingsTable } from '../utils/formatter.js';
|
|
10
|
+
import { CACHE_CONFIG, LEAGUES } from '../core/constants.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get league ID from league name
|
|
13
|
+
*/
|
|
14
|
+
function getLeagueId(leagueName) {
|
|
15
|
+
const normalized = leagueName.toLowerCase();
|
|
16
|
+
for (const [key, value] of Object.entries(LEAGUES)) {
|
|
17
|
+
if (value.name.toLowerCase().includes(normalized) ||
|
|
18
|
+
normalized.includes(value.name.toLowerCase()) ||
|
|
19
|
+
value.code?.toLowerCase() === normalized.replace(' ', '')) {
|
|
20
|
+
return value.id;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Try exact match
|
|
24
|
+
for (const [key, value] of Object.entries(LEAGUES)) {
|
|
25
|
+
if (value.name.toLowerCase() === normalized) {
|
|
26
|
+
return value.id;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// ============= Tool Registration =============
|
|
32
|
+
export function registerLeagueTools(server) {
|
|
33
|
+
/**
|
|
34
|
+
* Tool 1: get_league_standings
|
|
35
|
+
* Get current league standings table
|
|
36
|
+
*/
|
|
37
|
+
server.tool('get_league_standings_v2', `Get current league standings table.
|
|
38
|
+
Includes: position, played, won, drawn, lost, goals for/against, goal difference, points.
|
|
39
|
+
Supports home/away splits.`, {
|
|
40
|
+
league: z.string().describe('League name (e.g., "Premier League", "La Liga")'),
|
|
41
|
+
season: z.string().optional().describe('Season year (default: current)'),
|
|
42
|
+
}, async ({ league, season }) => {
|
|
43
|
+
const cache = getGlobalCache();
|
|
44
|
+
const currentYear = new Date().getFullYear();
|
|
45
|
+
const seasonYear = season || currentYear.toString();
|
|
46
|
+
const cacheKey = CacheService.generateKey('standings', league, seasonYear);
|
|
47
|
+
// Check cache first (30min TTL)
|
|
48
|
+
const cached = cache.get(cacheKey);
|
|
49
|
+
if (cached) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const leagueId = getLeagueId(league);
|
|
55
|
+
let output = '';
|
|
56
|
+
const errors = [];
|
|
57
|
+
if (!leagueId) {
|
|
58
|
+
errors.push(`League "${league}" not found. Available leagues: ${Object.values(LEAGUES).map(l => l.name).join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
if (leagueId) {
|
|
61
|
+
try {
|
|
62
|
+
const api = createAPIProvider();
|
|
63
|
+
const result = await api.getStandings(leagueId.toString(), seasonYear);
|
|
64
|
+
if (result.success && result.data) {
|
|
65
|
+
output = `## 🏆 ${league} - Standings ${seasonYear}\n\n`;
|
|
66
|
+
output += formatStandingsTable(result.data);
|
|
67
|
+
// Cache for 30 minutes
|
|
68
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.STANDINGS);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
errors.push(result.error || 'Unknown API error');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Fallback to web search if API failed
|
|
79
|
+
if (!output && errors.length > 0) {
|
|
80
|
+
try {
|
|
81
|
+
const searchQuery = `${league} standings table ${seasonYear}`;
|
|
82
|
+
const searchProvider = getSearchProvider();
|
|
83
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 3);
|
|
84
|
+
const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
85
|
+
output = `## ${league} - Standings ${seasonYear}\n\n`;
|
|
86
|
+
output += `*API providers not available. Click links for standings:*\n\n${fallbackText}`;
|
|
87
|
+
}
|
|
88
|
+
catch (searchError) {
|
|
89
|
+
output = `Error: ${errors.join(', ')}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: output }],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Tool 2: get_top_scorers
|
|
98
|
+
* Get top scorers for a league
|
|
99
|
+
*/
|
|
100
|
+
server.tool('get_top_scorers_v2', `Get top scorers for a league.`, {
|
|
101
|
+
league: z.string().describe('League name'),
|
|
102
|
+
limit: z.number().optional().default(20).describe('Number of players to return'),
|
|
103
|
+
season: z.string().optional().describe('Season year (default: current)'),
|
|
104
|
+
}, async ({ league, limit, season }) => {
|
|
105
|
+
const cache = getGlobalCache();
|
|
106
|
+
const currentYear = new Date().getFullYear();
|
|
107
|
+
const seasonYear = season || currentYear.toString();
|
|
108
|
+
const cacheKey = CacheService.generateKey('scorers', league, seasonYear, limit.toString());
|
|
109
|
+
// Check cache first
|
|
110
|
+
const cached = cache.get(cacheKey);
|
|
111
|
+
if (cached) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const leagueId = getLeagueId(league);
|
|
117
|
+
let output = '';
|
|
118
|
+
const errors = [];
|
|
119
|
+
if (!leagueId) {
|
|
120
|
+
errors.push(`League "${league}" not found`);
|
|
121
|
+
}
|
|
122
|
+
// Try API first
|
|
123
|
+
if (leagueId) {
|
|
124
|
+
try {
|
|
125
|
+
const api = createAPIProvider();
|
|
126
|
+
const searchQuery = `${league} top scorers ${seasonYear}`;
|
|
127
|
+
const searchProvider = getSearchProvider();
|
|
128
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, limit);
|
|
129
|
+
output = `## 🥇 ${league} - Top Scorers ${seasonYear}\n\n`;
|
|
130
|
+
if (searchResults.length > 0) {
|
|
131
|
+
const items = searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
132
|
+
output += items;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
output += 'No results found.';
|
|
136
|
+
}
|
|
137
|
+
// Cache the results
|
|
138
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.TEAM_STATS);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Fallback
|
|
145
|
+
if (!output) {
|
|
146
|
+
try {
|
|
147
|
+
const searchQuery = `${league} top scorers ${seasonYear}`;
|
|
148
|
+
const searchProvider = getSearchProvider();
|
|
149
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, limit);
|
|
150
|
+
output = `## 🥇 ${league} - Top Scorers ${seasonYear}\n\n`;
|
|
151
|
+
output += searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
output = `Error: ${errors.join(', ')}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: 'text', text: output }],
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Tool 3: get_league_fixtures
|
|
163
|
+
* Get upcoming fixtures for a league
|
|
164
|
+
*/
|
|
165
|
+
server.tool('get_league_fixtures', `Get upcoming fixtures for a league.`, {
|
|
166
|
+
league: z.string().describe('League name'),
|
|
167
|
+
days: z.number().optional().default(7).describe('Number of days ahead'),
|
|
168
|
+
}, async ({ league, days }) => {
|
|
169
|
+
const cache = getGlobalCache();
|
|
170
|
+
const cacheKey = CacheService.generateKey('fixtures', league, days.toString());
|
|
171
|
+
// Check cache first
|
|
172
|
+
const cached = cache.get(cacheKey);
|
|
173
|
+
if (cached) {
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const leagueId = getLeagueId(league);
|
|
179
|
+
let output = '';
|
|
180
|
+
try {
|
|
181
|
+
const searchQuery = `${league} fixtures schedule next ${days} days`;
|
|
182
|
+
const searchProvider = getSearchProvider();
|
|
183
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 10);
|
|
184
|
+
output = `## 📅 ${league} - Upcoming Fixtures\n\n`;
|
|
185
|
+
if (searchResults.length > 0) {
|
|
186
|
+
const items = searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n\n');
|
|
187
|
+
output += items;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
output += 'No fixtures found.';
|
|
191
|
+
}
|
|
192
|
+
// Cache for short period
|
|
193
|
+
cache.set(cacheKey, output, 15 * 60 * 1000); // 15 minutes
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
output = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: 'text', text: output }],
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -6,34 +6,65 @@ import { z } from 'zod';
|
|
|
6
6
|
import * as fs from 'fs/promises';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { getSearchProvider } from '../providers/search.js';
|
|
9
|
+
import { logger } from '../../../utils.js';
|
|
9
10
|
// Configuration for watchlist persistence
|
|
10
11
|
const WATCHLIST_FILE = process.env.SPORTS_WATCHLIST_PATH || '.sports_watchlist.json';
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// Watchlist storage with proper initialization
|
|
13
|
+
class WatchlistStorage {
|
|
14
|
+
items = [];
|
|
15
|
+
initialized = false;
|
|
16
|
+
initPromise = null;
|
|
17
|
+
async initialize() {
|
|
18
|
+
if (this.initialized)
|
|
19
|
+
return;
|
|
20
|
+
// Prevent concurrent initialization
|
|
21
|
+
if (this.initPromise)
|
|
22
|
+
return this.initPromise;
|
|
23
|
+
this.initPromise = this.loadFromFile();
|
|
24
|
+
await this.initPromise;
|
|
25
|
+
this.initialized = true;
|
|
19
26
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
async loadFromFile() {
|
|
28
|
+
try {
|
|
29
|
+
const filePath = path.resolve(WATCHLIST_FILE);
|
|
30
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
31
|
+
this.items = JSON.parse(data);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// File doesn't exist or is invalid, start with empty list
|
|
35
|
+
this.items = [];
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
async save() {
|
|
39
|
+
try {
|
|
40
|
+
const filePath = path.resolve(WATCHLIST_FILE);
|
|
41
|
+
await fs.writeFile(filePath, JSON.stringify(this.items, null, 2), 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.error(`Failed to save watchlist to ${WATCHLIST_FILE}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
getItems() {
|
|
48
|
+
return this.items;
|
|
49
|
+
}
|
|
50
|
+
add(item) {
|
|
51
|
+
this.items.push(item);
|
|
52
|
+
}
|
|
53
|
+
remove(id) {
|
|
54
|
+
const index = this.items.findIndex(item => item.id === id);
|
|
55
|
+
if (index === -1)
|
|
56
|
+
return null;
|
|
57
|
+
return this.items.splice(index, 1)[0];
|
|
58
|
+
}
|
|
59
|
+
clear() {
|
|
60
|
+
this.items = [];
|
|
30
61
|
}
|
|
31
|
-
|
|
32
|
-
|
|
62
|
+
get length() {
|
|
63
|
+
return this.items.length;
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
|
-
//
|
|
36
|
-
|
|
66
|
+
// Singleton instance
|
|
67
|
+
const watchlistStorage = new WatchlistStorage();
|
|
37
68
|
/**
|
|
38
69
|
* Register live and alert tools
|
|
39
70
|
*/
|
|
@@ -58,6 +89,8 @@ Can be called with:
|
|
|
58
89
|
awayTeam: z.string().optional().describe('Away team name'),
|
|
59
90
|
alertCondition: z.string().optional().describe('Alert condition for match'),
|
|
60
91
|
}, async ({ type, target, condition, matchId, homeTeam, awayTeam, alertCondition }) => {
|
|
92
|
+
// Ensure watchlist is initialized before use
|
|
93
|
+
await watchlistStorage.initialize();
|
|
61
94
|
const id = `watch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
62
95
|
// Handle match mode (matchId, homeTeam, awayTeam provided)
|
|
63
96
|
let itemType = type;
|
|
@@ -88,8 +121,8 @@ Can be called with:
|
|
|
88
121
|
createdAt: new Date().toISOString(),
|
|
89
122
|
triggered: false,
|
|
90
123
|
};
|
|
91
|
-
|
|
92
|
-
await
|
|
124
|
+
watchlistStorage.add(item);
|
|
125
|
+
await watchlistStorage.save();
|
|
93
126
|
let output = `## 📋 Added to Watchlist\n\n`;
|
|
94
127
|
output += `**ID:** ${id}\n`;
|
|
95
128
|
output += `**Type:** ${itemType}\n`;
|
|
@@ -100,7 +133,7 @@ Can be called with:
|
|
|
100
133
|
output += `**Match:** ${homeTeam} vs ${awayTeam}\n`;
|
|
101
134
|
}
|
|
102
135
|
output += `**Created:** ${item.createdAt}\n`;
|
|
103
|
-
output += `\n\nTotal watchlist items: ${
|
|
136
|
+
output += `\n\nTotal watchlist items: ${watchlistStorage.length}`;
|
|
104
137
|
return {
|
|
105
138
|
content: [{ type: 'text', text: output }],
|
|
106
139
|
};
|
|
@@ -110,13 +143,16 @@ Can be called with:
|
|
|
110
143
|
* List all items in the watchlist
|
|
111
144
|
*/
|
|
112
145
|
server.tool('watchlist_list', `List all items in the sports watchlist.`, {}, async () => {
|
|
113
|
-
|
|
146
|
+
// Ensure watchlist is initialized
|
|
147
|
+
await watchlistStorage.initialize();
|
|
148
|
+
const items = watchlistStorage.getItems();
|
|
149
|
+
if (items.length === 0) {
|
|
114
150
|
return {
|
|
115
151
|
content: [{ type: 'text', text: '## 📋 Watchlist\n\nNo items in watchlist.' }],
|
|
116
152
|
};
|
|
117
153
|
}
|
|
118
|
-
let output = `## 📋 Watchlist (${
|
|
119
|
-
for (const item of
|
|
154
|
+
let output = `## 📋 Watchlist (${items.length} items)\n\n`;
|
|
155
|
+
for (const item of items) {
|
|
120
156
|
const status = item.triggered ? '✅' : '👀';
|
|
121
157
|
output += `${status} **${item.id}**\n`;
|
|
122
158
|
output += `- Type: ${item.type}\n`;
|
|
@@ -136,20 +172,21 @@ Can be called with:
|
|
|
136
172
|
server.tool('watchlist_remove', `Remove an item from the sports watchlist.`, {
|
|
137
173
|
id: z.string().describe('Watchlist item ID to remove'),
|
|
138
174
|
}, async ({ id }) => {
|
|
139
|
-
|
|
140
|
-
|
|
175
|
+
// Ensure watchlist is initialized
|
|
176
|
+
await watchlistStorage.initialize();
|
|
177
|
+
const removed = watchlistStorage.remove(id);
|
|
178
|
+
if (!removed) {
|
|
141
179
|
return {
|
|
142
180
|
content: [{ type: 'text', text: `Error: Watchlist item "${id}" not found.` }],
|
|
143
181
|
isError: true,
|
|
144
182
|
};
|
|
145
183
|
}
|
|
146
|
-
|
|
147
|
-
await saveWatchlist();
|
|
184
|
+
await watchlistStorage.save();
|
|
148
185
|
let output = `## 📋 Removed from Watchlist\n\n`;
|
|
149
186
|
output += `**ID:** ${removed.id}\n`;
|
|
150
187
|
output += `**Type:** ${removed.type}\n`;
|
|
151
188
|
output += `**Target:** ${removed.target}\n`;
|
|
152
|
-
output += `\n\nRemaining items: ${
|
|
189
|
+
output += `\n\nRemaining items: ${watchlistStorage.length}`;
|
|
153
190
|
return {
|
|
154
191
|
content: [{ type: 'text', text: output }],
|
|
155
192
|
};
|
|
@@ -159,9 +196,11 @@ Can be called with:
|
|
|
159
196
|
* Clear all items from the watchlist
|
|
160
197
|
*/
|
|
161
198
|
server.tool('watchlist_clear', `Clear all items from the sports watchlist.`, {}, async () => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
// Ensure watchlist is initialized
|
|
200
|
+
await watchlistStorage.initialize();
|
|
201
|
+
const count = watchlistStorage.length;
|
|
202
|
+
watchlistStorage.clear();
|
|
203
|
+
await watchlistStorage.save();
|
|
165
204
|
return {
|
|
166
205
|
content: [{ type: 'text', text: `## 📋 Watchlist Cleared\n\nRemoved ${count} item(s).` }],
|
|
167
206
|
};
|
|
@@ -171,8 +210,11 @@ Can be called with:
|
|
|
171
210
|
* Check watchlist for triggered alerts
|
|
172
211
|
*/
|
|
173
212
|
server.tool('check_alerts', `Check watchlist for triggered alerts based on current data.`, {}, async () => {
|
|
213
|
+
// Ensure watchlist is initialized
|
|
214
|
+
await watchlistStorage.initialize();
|
|
174
215
|
let output = `## 🔔 Alert Check Results\n\n`;
|
|
175
|
-
|
|
216
|
+
const items = watchlistStorage.getItems();
|
|
217
|
+
if (items.length === 0) {
|
|
176
218
|
output += 'No items in watchlist.';
|
|
177
219
|
return {
|
|
178
220
|
content: [{ type: 'text', text: output }],
|
|
@@ -181,7 +223,7 @@ Can be called with:
|
|
|
181
223
|
const searchProvider = getSearchProvider();
|
|
182
224
|
let triggeredCount = 0;
|
|
183
225
|
let saveNeeded = false;
|
|
184
|
-
for (const item of
|
|
226
|
+
for (const item of items) {
|
|
185
227
|
let triggered = false;
|
|
186
228
|
let message = '';
|
|
187
229
|
// Check based on type
|
|
@@ -217,7 +259,7 @@ Can be called with:
|
|
|
217
259
|
}
|
|
218
260
|
}
|
|
219
261
|
if (saveNeeded) {
|
|
220
|
-
await
|
|
262
|
+
await watchlistStorage.save();
|
|
221
263
|
}
|
|
222
264
|
if (triggeredCount === 0) {
|
|
223
265
|
output += 'No new alerts. All conditions normal.';
|