@llmindset/hf-mcp 0.1.16

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/dist/dataset-detail.d.ts +26 -0
  3. package/dist/dataset-detail.d.ts.map +1 -0
  4. package/dist/dataset-detail.js +157 -0
  5. package/dist/dataset-detail.js.map +1 -0
  6. package/dist/dataset-search.d.ts +62 -0
  7. package/dist/dataset-search.d.ts.map +1 -0
  8. package/dist/dataset-search.js +158 -0
  9. package/dist/dataset-search.js.map +1 -0
  10. package/dist/duplicate-space.d.ts +75 -0
  11. package/dist/duplicate-space.d.ts.map +1 -0
  12. package/dist/duplicate-space.js +189 -0
  13. package/dist/duplicate-space.js.map +1 -0
  14. package/dist/error-messages.d.ts +4 -0
  15. package/dist/error-messages.d.ts.map +1 -0
  16. package/dist/error-messages.js +30 -0
  17. package/dist/error-messages.js.map +1 -0
  18. package/dist/hf-api-call.d.ts +18 -0
  19. package/dist/hf-api-call.d.ts.map +1 -0
  20. package/dist/hf-api-call.js +105 -0
  21. package/dist/hf-api-call.js.map +1 -0
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +16 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/model-detail.d.ts +26 -0
  27. package/dist/model-detail.d.ts.map +1 -0
  28. package/dist/model-detail.js +224 -0
  29. package/dist/model-detail.js.map +1 -0
  30. package/dist/model-search.d.ts +64 -0
  31. package/dist/model-search.d.ts.map +1 -0
  32. package/dist/model-search.js +161 -0
  33. package/dist/model-search.js.map +1 -0
  34. package/dist/paper-search.d.ts +58 -0
  35. package/dist/paper-search.d.ts.map +1 -0
  36. package/dist/paper-search.js +114 -0
  37. package/dist/paper-search.js.map +1 -0
  38. package/dist/paper-summary.d.ts +35 -0
  39. package/dist/paper-summary.d.ts.map +1 -0
  40. package/dist/paper-summary.js +187 -0
  41. package/dist/paper-summary.js.map +1 -0
  42. package/dist/space-files.d.ts +44 -0
  43. package/dist/space-files.d.ts.map +1 -0
  44. package/dist/space-files.js +242 -0
  45. package/dist/space-files.js.map +1 -0
  46. package/dist/space-info.d.ts +56 -0
  47. package/dist/space-info.d.ts.map +1 -0
  48. package/dist/space-info.js +135 -0
  49. package/dist/space-info.js.map +1 -0
  50. package/dist/space-search.d.ts +71 -0
  51. package/dist/space-search.d.ts.map +1 -0
  52. package/dist/space-search.js +95 -0
  53. package/dist/space-search.js.map +1 -0
  54. package/dist/tool-ids.d.ts +23 -0
  55. package/dist/tool-ids.d.ts.map +1 -0
  56. package/dist/tool-ids.js +55 -0
  57. package/dist/tool-ids.js.map +1 -0
  58. package/dist/user-summary.d.ts +56 -0
  59. package/dist/user-summary.d.ts.map +1 -0
  60. package/dist/user-summary.js +271 -0
  61. package/dist/user-summary.js.map +1 -0
  62. package/dist/utilities.d.ts +8 -0
  63. package/dist/utilities.d.ts.map +1 -0
  64. package/dist/utilities.js +53 -0
  65. package/dist/utilities.js.map +1 -0
  66. package/eslint.config.js +43 -0
  67. package/package.json +47 -0
  68. package/src/dataset-detail.ts +257 -0
  69. package/src/dataset-search.ts +237 -0
  70. package/src/duplicate-space.ts +263 -0
  71. package/src/error-messages.ts +57 -0
  72. package/src/hf-api-call.ts +182 -0
  73. package/src/index.ts +18 -0
  74. package/src/model-detail.ts +359 -0
  75. package/src/model-search.ts +231 -0
  76. package/src/paper-search.ts +188 -0
  77. package/src/paper-summary.ts +303 -0
  78. package/src/space-files.ts +325 -0
  79. package/src/space-info.ts +190 -0
  80. package/src/space-search.ts +177 -0
  81. package/src/tool-ids.ts +84 -0
  82. package/src/user-summary.ts +421 -0
  83. package/src/utilities.ts +64 -0
  84. package/test/duplicate-space.spec.ts +41 -0
  85. package/test/fixtures/paper_result_kazakh.json +854 -0
  86. package/test/fixtures/space-result.json +263 -0
  87. package/test/paper-search.spec.ts +57 -0
  88. package/test/paper-summary.spec.ts +113 -0
  89. package/test/space-files.spec.ts +232 -0
  90. package/test/space-search.spec.ts +29 -0
  91. package/test/user-summary.spec.ts +131 -0
  92. package/tsconfig.json +31 -0
  93. package/vitest.config.ts +11 -0
@@ -0,0 +1,263 @@
1
+ import { z } from 'zod';
2
+ import { HfApiCall } from './hf-api-call.js';
3
+ import { explain } from './error-messages.js';
4
+ import { NO_TOKEN_INSTRUCTIONS } from './utilities.js';
5
+
6
+ export interface SpaceInfo {
7
+ runtime?: {
8
+ hardware?:
9
+ | {
10
+ current?: string;
11
+ requested?: string;
12
+ }
13
+ | string;
14
+ };
15
+ gated?: boolean;
16
+ models?: string[];
17
+ }
18
+
19
+ export interface SpaceVariable {
20
+ key: string;
21
+ value: string;
22
+ description?: string;
23
+ }
24
+
25
+ export interface DuplicateSpaceParams {
26
+ sourceSpaceId: string;
27
+ newSpaceName?: string;
28
+ hardware?: 'freecpu' | 'zerogpu';
29
+ private?: boolean;
30
+ }
31
+
32
+ export interface DuplicateSpaceResult {
33
+ url: string;
34
+ spaceId: string;
35
+ hardware: string;
36
+ private: boolean;
37
+ variablesCopied: number;
38
+ instructions: string;
39
+ hardwareWarning?: string;
40
+ }
41
+
42
+ export const DUPLICATE_SPACE_TOOL_CONFIG = {
43
+ name: 'duplicate_space',
44
+ description: '', // This will be dynamically set with username
45
+ schema: z.object({
46
+ sourceSpaceId: z.string().min(1).describe("Space ID to copy (e.g., 'username/space-name')"),
47
+ newSpaceId: z.string().optional().describe('Name for the new space (optional, defaults to source space-name)'),
48
+ hardware: z
49
+ .enum(['freecpu', 'zerogpu'])
50
+ .optional()
51
+ .describe('Either "freecpu" or "zerogpu" (defaults based on source). Both options are in the free tier.'),
52
+ private: z
53
+ .boolean()
54
+ .optional()
55
+ .default(true)
56
+ .describe('Check with User whether the new space should be public or private.'),
57
+ }),
58
+ annotations: {
59
+ title: 'Duplicate Hugging Face Space',
60
+ destructiveHint: false,
61
+ readOnlyHint: false,
62
+ openWorldHint: true,
63
+ },
64
+ } as const;
65
+
66
+ // Hardware mapping constants
67
+ const HARDWARE_MAP = {
68
+ freecpu: 'cpu-basic',
69
+ zerogpu: 'zero-a10g',
70
+ } as const;
71
+
72
+ const FREE_HARDWARE = ['cpu-basic', 'zero-a10g'];
73
+
74
+ export class DuplicateSpaceTool extends HfApiCall<DuplicateSpaceParams, DuplicateSpaceResult> {
75
+ private username?: string;
76
+
77
+ constructor(hfToken?: string, username?: string) {
78
+ super('https://huggingface.co/api', hfToken);
79
+ this.username = username;
80
+ }
81
+
82
+ static createToolConfig(
83
+ username?: string
84
+ ): Omit<typeof DUPLICATE_SPACE_TOOL_CONFIG, 'description'> & { description: string } {
85
+ const description = username
86
+ ? `Duplicate a Hugging Face Space. Target space will be created as ${username}/<new-space-name>.`
87
+ : NO_TOKEN_INSTRUCTIONS;
88
+ return {
89
+ ...DUPLICATE_SPACE_TOOL_CONFIG,
90
+ description,
91
+ };
92
+ }
93
+
94
+ normalizeSpaceName(spaceName: string): string {
95
+ // If already has a slash, check if it's trying to use a different username
96
+ if (spaceName.includes('/')) {
97
+ const [providedUser, spaceNamePart] = spaceName.split('/');
98
+ if (providedUser !== this.username) {
99
+ throw new Error(
100
+ `Invalid space ID: ${spaceName}. You can only create spaces in your own namespace. Try "${this.username || 'your-username'}/${spaceNamePart || 'space-name'}"`
101
+ );
102
+ }
103
+ return spaceName;
104
+ }
105
+ // Otherwise, prepend with username
106
+ return `${this.username || 'unknown'}/${spaceName}`;
107
+ }
108
+
109
+ async getSpaceInfo(spaceId: string): Promise<SpaceInfo> {
110
+ const url = `${this.apiUrl}/spaces/${spaceId}`;
111
+ return this.fetchFromApi(url);
112
+ }
113
+
114
+ async getSpaceVariables(spaceId: string): Promise<Record<string, { value: string; description?: string }>> {
115
+ const url = `${this.apiUrl}/spaces/${spaceId}/variables`;
116
+ try {
117
+ return await this.fetchFromApi(url);
118
+ } catch {
119
+ // If we can't access variables (private space or no permissions), return empty
120
+ return {};
121
+ }
122
+ }
123
+
124
+ async duplicate(params: DuplicateSpaceParams): Promise<DuplicateSpaceResult> {
125
+ const { sourceSpaceId, newSpaceName, hardware, private: isPrivate = true } = params;
126
+
127
+ if (!this.username) throw new Error(NO_TOKEN_INSTRUCTIONS);
128
+
129
+ try {
130
+ // Step 1: Get source space info
131
+ let sourceInfo: SpaceInfo;
132
+ try {
133
+ sourceInfo = await this.getSpaceInfo(sourceSpaceId);
134
+ } catch (error) {
135
+ // Explain the error and rethrow
136
+ throw explain(error, `Could not access source space "${sourceSpaceId}"`);
137
+ }
138
+
139
+ // Step 2: Get variables from source space
140
+ const sourceVars = await this.getSpaceVariables(sourceSpaceId);
141
+ const variables: SpaceVariable[] = Object.entries(sourceVars).map(([key, varInfo]) => ({
142
+ key,
143
+ value: varInfo.value,
144
+ description: varInfo.description,
145
+ }));
146
+
147
+ // Step 3: Determine hardware
148
+ // Extract hardware string from either object or string format
149
+ let sourceHardwareStr: string | undefined;
150
+ if (typeof sourceInfo.runtime?.hardware === 'object') {
151
+ sourceHardwareStr = sourceInfo.runtime.hardware.current || sourceInfo.runtime.hardware.requested;
152
+ } else {
153
+ sourceHardwareStr = sourceInfo.runtime?.hardware;
154
+ }
155
+
156
+ let selectedHardware: string;
157
+ let hardwareKey: 'freecpu' | 'zerogpu';
158
+
159
+ if (hardware) {
160
+ selectedHardware = HARDWARE_MAP[hardware];
161
+ hardwareKey = hardware;
162
+ } else {
163
+ // Auto-detect based on source
164
+ if (sourceHardwareStr === 'zero-a10g') {
165
+ selectedHardware = 'zero-a10g';
166
+ hardwareKey = 'zerogpu';
167
+ } else if (sourceHardwareStr === 'cpu-basic' || !sourceHardwareStr) {
168
+ selectedHardware = 'cpu-basic';
169
+ hardwareKey = 'freecpu';
170
+ } else {
171
+ // For any paid hardware, default to ZeroGPU (free tier)
172
+ selectedHardware = 'zero-a10g';
173
+ hardwareKey = 'zerogpu';
174
+ }
175
+ }
176
+
177
+ // Step 4: Determine target space ID
178
+ let targetId: string;
179
+ if (newSpaceName) {
180
+ targetId = this.normalizeSpaceName(newSpaceName);
181
+ } else {
182
+ // Use same name as source
183
+ const sourceName = sourceSpaceId.split('/')[1];
184
+ targetId = `${this.username}/${sourceName || ''}`;
185
+ }
186
+
187
+ // Step 5: Check for warnings
188
+ let hardwareWarning: string | undefined;
189
+ if (sourceHardwareStr && !FREE_HARDWARE.includes(sourceHardwareStr)) {
190
+ hardwareWarning = `Note: The source space uses '${sourceHardwareStr}' which is paid hardware. Your duplicated space is set to '${hardwareKey}'. "+
191
+ "You may need to upgrade in Settings to run this space or achieve the same performance.`;
192
+ }
193
+
194
+ let gatedWarning: string | undefined;
195
+ if (sourceInfo.gated) {
196
+ gatedWarning = `🔐 The model in this space is 'gated' - you may need to accept the licensing conditions before use.`;
197
+ }
198
+
199
+ // Step 6: Make the duplication request
200
+ const url = `${this.apiUrl}/spaces/${sourceSpaceId}/duplicate`;
201
+ const payload = {
202
+ repository: targetId,
203
+ private: isPrivate,
204
+ hardware: selectedHardware,
205
+ variables: variables.length > 0 ? variables : undefined,
206
+ };
207
+
208
+ const response = await this.fetchFromApi<{ url: string }>(url, {
209
+ method: 'POST',
210
+ body: JSON.stringify(payload),
211
+ });
212
+
213
+ // Step 7: Construct response
214
+ const warnings: string[] = [];
215
+ if (hardwareWarning) warnings.push(hardwareWarning);
216
+ if (gatedWarning) warnings.push(gatedWarning);
217
+
218
+ const result: DuplicateSpaceResult = {
219
+ url: response.url,
220
+ spaceId: targetId,
221
+ hardware: hardwareKey,
222
+ private: isPrivate,
223
+ variablesCopied: variables.length,
224
+ instructions: this.formatInstructions(response.url, hardwareKey, isPrivate, warnings),
225
+ };
226
+
227
+ if (hardwareWarning) {
228
+ result.hardwareWarning = hardwareWarning;
229
+ }
230
+
231
+ return result;
232
+ } catch (error) {
233
+ // Explain the error and rethrow
234
+ throw explain(error, 'Failed to duplicate space');
235
+ }
236
+ }
237
+
238
+ private formatInstructions(url: string, hardware: string, isPrivate: boolean, warnings: string[]): string {
239
+ let instructions = `✅ 🤗 Space successfully duplicated!
240
+
241
+ 🔗 Your new space: ${url}
242
+
243
+ ⚙️ To configure your space:
244
+ 1. Go to ${url}
245
+ 2. Click on 'Settings' in the top right
246
+ 3. Configure any additional settings as needed
247
+
248
+ Hardware: ${hardware} | Visibility: ${isPrivate ? 'Private' : 'Public'}`;
249
+
250
+ if (warnings.length > 0) {
251
+ instructions += '\n\n⚠️ Warnings:';
252
+ warnings.forEach((warning) => {
253
+ instructions += `\n- ${warning}`;
254
+ });
255
+ }
256
+
257
+ return instructions;
258
+ }
259
+ }
260
+
261
+ export const formatDuplicateResult = (result: DuplicateSpaceResult): string => {
262
+ return result.instructions;
263
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Common HTTP error messages for Hugging Face API operations
3
+ */
4
+
5
+ import { HfApiError } from './hf-api-call.js';
6
+
7
+ /**
8
+ * Friendly explanations for common HTTP error codes
9
+ */
10
+ export const FRIENDLY_ERROR_EXPLANATIONS: Record<number, string> = {
11
+ 400: 'The request was invalid. Check your HF_TOKEN permissions, or that you have not exceeded the maximum number of spaces allowed..',
12
+ 401: 'Authentication failed. Please check that your Hugging Face token is valid. Check your Hugging Face token permissions.',
13
+ 403: 'You do not have permission to access this resource. Check your Hugging Face token permissions.',
14
+ 404: 'The requested resource does not exist.',
15
+ 409: 'A resource with this name already exists.',
16
+ 422: 'The provided configuration is invalid.',
17
+ 429: 'Duplication failed due to rate limits.',
18
+ 500: 'Hugging Face is experiencing issues. Please try again later.',
19
+ 502: 'The server gateway is temporarily unavailable.',
20
+ 503: 'The service is temporarily down for maintenance.',
21
+ };
22
+
23
+ /**
24
+ * Get friendly explanation for a given HTTP status code
25
+ */
26
+ export function getFriendlyExplanation(status: number): string {
27
+ // Check for exact match first
28
+ if (FRIENDLY_ERROR_EXPLANATIONS[status]) {
29
+ return FRIENDLY_ERROR_EXPLANATIONS[status];
30
+ }
31
+
32
+ // Handle 5xx errors generically
33
+ if (status >= 500) {
34
+ return 'Hugging Face is experiencing issues. Please try again later.';
35
+ }
36
+
37
+ // Default for unknown errors
38
+ return 'An unexpected error occurred. Please try again.';
39
+ }
40
+
41
+ /**
42
+ * Explain an error by enhancing it with friendly explanation if it's an HfApiError
43
+ * Otherwise returns the original error unchanged
44
+ * @param error - The error to explain
45
+ * @param context - Optional context about what was being attempted
46
+ * @returns The enhanced HfApiError or original error
47
+ */
48
+ export function explain(error: unknown, context?: string): unknown {
49
+ // Only enhance HfApiError instances
50
+ if (error instanceof HfApiError) {
51
+ const friendlyExplanation = getFriendlyExplanation(error.status);
52
+ return error.withImprovedMessage(friendlyExplanation, context);
53
+ }
54
+
55
+ // Return any other error unchanged
56
+ return error;
57
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Custom error class that includes HTTP status information
3
+ */
4
+ export class HfApiError extends Error {
5
+ constructor(
6
+ message: string,
7
+ public readonly status: number,
8
+ public readonly statusText: string,
9
+ public readonly responseBody?: string
10
+ ) {
11
+ super(message);
12
+ this.name = 'HfApiError';
13
+ }
14
+
15
+ /**
16
+ * Format the error with a friendly explanation followed by the original error
17
+ * @param friendlyExplanation - User-friendly explanation
18
+ * @param context - Optional context about what was being attempted
19
+ * @returns Formatted error message
20
+ */
21
+ formatWithExplanation(friendlyExplanation: string, context?: string): string {
22
+ let formatted = '';
23
+
24
+ // Add context if provided
25
+ if (context) {
26
+ formatted = `${context}. `;
27
+ }
28
+
29
+ // Add friendly explanation
30
+ formatted += friendlyExplanation;
31
+
32
+ // Add original error message on new line
33
+ formatted += `\n\n${this.message}`;
34
+
35
+ // Add response body details if available and different from message
36
+ if (this.responseBody) {
37
+ try {
38
+ const parsed = JSON.parse(this.responseBody) as { error?: string; message?: string; detail?: string };
39
+ const errorDetail = parsed.error || parsed.message || parsed.detail;
40
+ if (errorDetail && !this.message.includes(errorDetail)) {
41
+ formatted += `\n${errorDetail}`;
42
+ }
43
+ } catch {
44
+ // If not JSON, add raw response if it's not too long and not already in message
45
+ if (this.responseBody.length < 200 && !this.message.includes(this.responseBody)) {
46
+ formatted += `\n${this.responseBody}`;
47
+ }
48
+ }
49
+ }
50
+
51
+ return formatted;
52
+ }
53
+
54
+ /**
55
+ * Create a new HfApiError with an improved message while preserving all other properties
56
+ * @param friendlyExplanation - User-friendly explanation
57
+ * @param context - Optional context about what was being attempted
58
+ * @returns New HfApiError with improved message
59
+ */
60
+ withImprovedMessage(friendlyExplanation: string, context?: string): HfApiError {
61
+ const improvedMessage = this.formatWithExplanation(friendlyExplanation, context);
62
+ return new HfApiError(improvedMessage, this.status, this.statusText, this.responseBody);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Base API client for Hugging Face HTTP APIs
68
+ *
69
+ * @template TParams - Type for API parameters
70
+ * @template TResponse - Type for API response
71
+ */
72
+ export class HfApiCall<TParams = Record<string, string | undefined>, TResponse = unknown> {
73
+ protected readonly apiUrl: string;
74
+ protected readonly hfToken: string | undefined;
75
+ protected readonly apiTimeout: number;
76
+
77
+ /** nb reversed order from superclasses on basis that hfToken is more likely to be configured */
78
+ constructor(apiUrl: string, hfToken?: string) {
79
+ this.apiUrl = apiUrl;
80
+ this.hfToken = hfToken;
81
+ // Default to 12.5 seconds if HF_API_TIMEOUT is not set
82
+ this.apiTimeout = process.env.HF_API_TIMEOUT ? parseInt(process.env.HF_API_TIMEOUT, 10) : 12500;
83
+ }
84
+
85
+ /**
86
+ * Fetches data from the API with proper error handling and authentication
87
+ *
88
+ * @template T - Response type (defaults to TResponse)
89
+ * @param url - The URL to fetch from
90
+ * @param options - Fetch options
91
+ * @returns The parsed JSON response
92
+ */
93
+ protected async fetchFromApi<T = TResponse>(url: URL | string, options?: globalThis.RequestInit): Promise<T> {
94
+ try {
95
+ const headers: Record<string, string> = {
96
+ Accept: 'application/json',
97
+ ...((options?.headers as Record<string, string>) || {}),
98
+ };
99
+ if (this.hfToken) {
100
+ headers['Authorization'] = `Bearer ${this.hfToken}`;
101
+ }
102
+
103
+ // Add timeout using AbortController
104
+ const controller = new AbortController();
105
+ const timeoutId = setTimeout(() => controller.abort(), this.apiTimeout);
106
+
107
+ const response = await fetch(url.toString(), {
108
+ ...options,
109
+ headers,
110
+ signal: controller.signal,
111
+ });
112
+
113
+ clearTimeout(timeoutId);
114
+
115
+ if (!response.ok) {
116
+ // Try to get error details from response body
117
+ let responseBody: string | undefined;
118
+ try {
119
+ responseBody = await response.text();
120
+ } catch {
121
+ // Ignore if we can't read the body
122
+ }
123
+
124
+ throw new HfApiError(
125
+ `API request failed: ${response.status.toString()} ${response.statusText}`,
126
+ response.status,
127
+ response.statusText,
128
+ responseBody
129
+ );
130
+ }
131
+
132
+ return (await response.json()) as T;
133
+ } catch (error) {
134
+ // Re-throw HfApiError as-is to preserve status information
135
+ if (error instanceof HfApiError) {
136
+ throw error;
137
+ }
138
+ // Handle timeout errors
139
+ if (error instanceof Error && error.name === 'AbortError') {
140
+ throw new Error(`API request timed out after ${this.apiTimeout}ms`);
141
+ }
142
+ // Wrap other errors
143
+ if (error instanceof Error) {
144
+ throw new Error(`API request failed: ${error.message}`);
145
+ }
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Builds a URL with query parameters
152
+ *
153
+ * @param params - Key-value pairs of query parameters
154
+ * @returns A URL object with the query parameters appended
155
+ */
156
+ protected buildUrl(params: TParams): URL {
157
+ const url = new URL(this.apiUrl);
158
+
159
+ // Iterate over params in a type-safe way
160
+ for (const key in params) {
161
+ const value = params[key as keyof TParams];
162
+ if (value !== undefined) {
163
+ url.searchParams.append(key, String(value));
164
+ }
165
+ }
166
+
167
+ return url;
168
+ }
169
+
170
+ /**
171
+ * Builds a URL with the given parameters and makes an API request
172
+ *
173
+ * @template T - Response type (defaults to TResponse)
174
+ * @param params - The parameters to include in the URL
175
+ * @param options - Additional fetch options
176
+ * @returns The parsed JSON response
177
+ */
178
+ protected async callApi<T = TResponse>(params: TParams, options?: globalThis.RequestInit): Promise<T> {
179
+ const url = this.buildUrl(params);
180
+ return this.fetchFromApi<T>(url, options);
181
+ }
182
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ // Export all modules - types are included in each module
2
+ export * from './hf-api-call.js';
3
+ export * from './error-messages.js';
4
+ export * from './space-search.js';
5
+ export * from './model-search.js';
6
+ export * from './model-detail.js';
7
+ export * from './utilities.js';
8
+ export * from './paper-search.js';
9
+ export * from './dataset-search.js';
10
+ export * from './dataset-detail.js';
11
+ export * from './duplicate-space.js';
12
+ export * from './space-info.js';
13
+ export * from './space-files.js';
14
+ export * from './user-summary.js';
15
+ export * from './paper-summary.js';
16
+
17
+ // Export tool IDs for external use - these are the canonical tool identifiers
18
+ export * from './tool-ids.js';