@ctxprotocol/sdk 0.5.5 → 0.6.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/README.md +123 -47
- package/dist/client/index.cjs +80 -26
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +20 -6
- package/dist/client/index.d.ts +20 -6
- package/dist/client/index.js +80 -26
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +176 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -16
- package/dist/index.d.ts +270 -16
- package/dist/index.js +169 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/client/types.ts
|
|
2
|
-
var ContextError = class extends Error {
|
|
2
|
+
var ContextError = class _ContextError extends Error {
|
|
3
3
|
constructor(message, code, statusCode, helpUrl) {
|
|
4
4
|
super(message);
|
|
5
5
|
this.code = code;
|
|
6
6
|
this.statusCode = statusCode;
|
|
7
7
|
this.helpUrl = helpUrl;
|
|
8
8
|
this.name = "ContextError";
|
|
9
|
+
Object.setPrototypeOf(this, _ContextError.prototype);
|
|
9
10
|
}
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -38,7 +39,7 @@ var Discovery = class {
|
|
|
38
39
|
}
|
|
39
40
|
const queryString = params.toString();
|
|
40
41
|
const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
|
|
41
|
-
const response = await this.client.
|
|
42
|
+
const response = await this.client._fetch(endpoint);
|
|
42
43
|
return response.tools;
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
@@ -95,7 +96,7 @@ var Tools = class {
|
|
|
95
96
|
*/
|
|
96
97
|
async execute(options) {
|
|
97
98
|
const { toolId, toolName, args } = options;
|
|
98
|
-
const response = await this.client.
|
|
99
|
+
const response = await this.client._fetch(
|
|
99
100
|
"/api/v1/tools/execute",
|
|
100
101
|
{
|
|
101
102
|
method: "POST",
|
|
@@ -106,7 +107,8 @@ var Tools = class {
|
|
|
106
107
|
throw new ContextError(
|
|
107
108
|
response.error,
|
|
108
109
|
response.code,
|
|
109
|
-
|
|
110
|
+
void 0,
|
|
111
|
+
// Don't hardcode - this was a 200 OK with error body
|
|
110
112
|
response.helpUrl
|
|
111
113
|
);
|
|
112
114
|
}
|
|
@@ -125,6 +127,7 @@ var Tools = class {
|
|
|
125
127
|
var ContextClient = class {
|
|
126
128
|
apiKey;
|
|
127
129
|
baseUrl;
|
|
130
|
+
_closed = false;
|
|
128
131
|
/**
|
|
129
132
|
* Discovery resource for searching tools
|
|
130
133
|
*/
|
|
@@ -149,38 +152,89 @@ var ContextClient = class {
|
|
|
149
152
|
this.discovery = new Discovery(this);
|
|
150
153
|
this.tools = new Tools(this);
|
|
151
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Close the client and clean up resources.
|
|
157
|
+
* After calling close(), any in-flight requests may be aborted.
|
|
158
|
+
*/
|
|
159
|
+
close() {
|
|
160
|
+
this._closed = true;
|
|
161
|
+
}
|
|
152
162
|
/**
|
|
153
163
|
* Internal method for making authenticated HTTP requests
|
|
154
|
-
*
|
|
164
|
+
* Includes timeout (30s) and retry with exponential backoff for transient errors
|
|
155
165
|
*
|
|
156
166
|
* @internal
|
|
157
167
|
*/
|
|
158
|
-
async
|
|
168
|
+
async _fetch(endpoint, options = {}) {
|
|
169
|
+
if (this._closed) {
|
|
170
|
+
throw new ContextError("Client has been closed");
|
|
171
|
+
}
|
|
159
172
|
const url = `${this.baseUrl}${endpoint}`;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
if (!response.ok) {
|
|
169
|
-
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
170
|
-
let errorCode;
|
|
171
|
-
let helpUrl;
|
|
173
|
+
const maxRetries = 3;
|
|
174
|
+
const timeoutMs = 3e4;
|
|
175
|
+
let lastError;
|
|
176
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
172
179
|
try {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const response = await fetch(url, {
|
|
181
|
+
...options,
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
headers: {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
186
|
+
...options.headers
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
192
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
197
|
+
let errorCode;
|
|
198
|
+
let helpUrl;
|
|
199
|
+
try {
|
|
200
|
+
const errorBody = await response.json();
|
|
201
|
+
if (errorBody.error) {
|
|
202
|
+
errorMessage = errorBody.error;
|
|
203
|
+
errorCode = errorBody.code;
|
|
204
|
+
helpUrl = errorBody.helpUrl;
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
209
|
+
}
|
|
210
|
+
return response.json();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
clearTimeout(timeout);
|
|
213
|
+
if (error instanceof ContextError) {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
217
|
+
const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
|
|
218
|
+
if (isRetryable && attempt < maxRetries) {
|
|
219
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (lastError.name === "AbortError") {
|
|
224
|
+
throw new ContextError(
|
|
225
|
+
`Request timed out after ${timeoutMs / 1e3}s`,
|
|
226
|
+
void 0,
|
|
227
|
+
408
|
|
228
|
+
);
|
|
178
229
|
}
|
|
179
|
-
|
|
230
|
+
throw new ContextError(
|
|
231
|
+
lastError.message,
|
|
232
|
+
void 0,
|
|
233
|
+
void 0
|
|
234
|
+
);
|
|
180
235
|
}
|
|
181
|
-
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
182
236
|
}
|
|
183
|
-
|
|
237
|
+
throw lastError ?? new ContextError("Request failed after retries");
|
|
184
238
|
}
|
|
185
239
|
};
|
|
186
240
|
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/types.ts","../../src/client/resources/discovery.ts","../../src/client/resources/tools.ts","../../src/client/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/types.ts","../../src/client/resources/discovery.ts","../../src/client/resources/tools.ts","../../src/client/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /** Tool type - currently always \"mcp\" */\n kind?: string;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n // Trust metrics (Level 2 - Reputation Ledger)\n /** Total number of queries processed */\n totalQueries?: number;\n\n /** Success rate percentage (0-100) */\n successRate?: string;\n\n /** Uptime percentage (0-100) */\n uptimePercent?: string;\n\n /** Total USDC staked by the developer */\n totalStaked?: string;\n\n /** Whether the tool has \"Proven\" status (100+ queries, >95% success, >98% uptime) */\n isProven?: boolean;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n Object.setPrototypeOf(this, ContextError.prototype);\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client._fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client._fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n undefined, // Don't hardcode - this was a 200 OK with error body\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private _closed = false;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Close the client and clean up resources.\n * After calling close(), any in-flight requests may be aborted.\n */\n close(): void {\n this._closed = true;\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * Includes timeout (30s) and retry with exponential backoff for transient errors\n *\n * @internal\n */\n async _fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n if (this._closed) {\n throw new ContextError(\"Client has been closed\");\n }\n\n const url = `${this.baseUrl}${endpoint}`;\n const maxRetries = 3;\n const timeoutMs = 30_000;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n clearTimeout(timeout);\n\n if (!response.ok) {\n // Retry on 5xx server errors\n if (response.status >= 500 && attempt < maxRetries) {\n const delay = Math.min(1000 * 2 ** attempt, 10_000);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeout);\n\n if (error instanceof ContextError) {\n throw error;\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Retry on network errors and timeouts\n const isRetryable =\n lastError.name === \"AbortError\" ||\n lastError.message.includes(\"fetch failed\") ||\n lastError.message.includes(\"ECONNRESET\") ||\n lastError.message.includes(\"ETIMEDOUT\");\n\n if (isRetryable && attempt < maxRetries) {\n const delay = Math.min(1000 * 2 ** attempt, 10_000);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n if (lastError.name === \"AbortError\") {\n throw new ContextError(\n `Request timed out after ${timeoutMs / 1000}s`,\n undefined,\n 408\n );\n }\n\n throw new ContextError(\n lastError.message,\n undefined,\n undefined\n );\n }\n }\n\n throw lastError ?? new ContextError(\"Request failed after retries\");\n }\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
var jose = require('jose');
|
|
4
4
|
|
|
5
5
|
// src/client/types.ts
|
|
6
|
-
var ContextError = class extends Error {
|
|
6
|
+
var ContextError = class _ContextError extends Error {
|
|
7
7
|
constructor(message, code, statusCode, helpUrl) {
|
|
8
8
|
super(message);
|
|
9
9
|
this.code = code;
|
|
10
10
|
this.statusCode = statusCode;
|
|
11
11
|
this.helpUrl = helpUrl;
|
|
12
12
|
this.name = "ContextError";
|
|
13
|
+
Object.setPrototypeOf(this, _ContextError.prototype);
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -42,7 +43,7 @@ var Discovery = class {
|
|
|
42
43
|
}
|
|
43
44
|
const queryString = params.toString();
|
|
44
45
|
const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
|
|
45
|
-
const response = await this.client.
|
|
46
|
+
const response = await this.client._fetch(endpoint);
|
|
46
47
|
return response.tools;
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
@@ -99,7 +100,7 @@ var Tools = class {
|
|
|
99
100
|
*/
|
|
100
101
|
async execute(options) {
|
|
101
102
|
const { toolId, toolName, args } = options;
|
|
102
|
-
const response = await this.client.
|
|
103
|
+
const response = await this.client._fetch(
|
|
103
104
|
"/api/v1/tools/execute",
|
|
104
105
|
{
|
|
105
106
|
method: "POST",
|
|
@@ -110,7 +111,8 @@ var Tools = class {
|
|
|
110
111
|
throw new ContextError(
|
|
111
112
|
response.error,
|
|
112
113
|
response.code,
|
|
113
|
-
|
|
114
|
+
void 0,
|
|
115
|
+
// Don't hardcode - this was a 200 OK with error body
|
|
114
116
|
response.helpUrl
|
|
115
117
|
);
|
|
116
118
|
}
|
|
@@ -129,6 +131,7 @@ var Tools = class {
|
|
|
129
131
|
var ContextClient = class {
|
|
130
132
|
apiKey;
|
|
131
133
|
baseUrl;
|
|
134
|
+
_closed = false;
|
|
132
135
|
/**
|
|
133
136
|
* Discovery resource for searching tools
|
|
134
137
|
*/
|
|
@@ -153,43 +156,95 @@ var ContextClient = class {
|
|
|
153
156
|
this.discovery = new Discovery(this);
|
|
154
157
|
this.tools = new Tools(this);
|
|
155
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Close the client and clean up resources.
|
|
161
|
+
* After calling close(), any in-flight requests may be aborted.
|
|
162
|
+
*/
|
|
163
|
+
close() {
|
|
164
|
+
this._closed = true;
|
|
165
|
+
}
|
|
156
166
|
/**
|
|
157
167
|
* Internal method for making authenticated HTTP requests
|
|
158
|
-
*
|
|
168
|
+
* Includes timeout (30s) and retry with exponential backoff for transient errors
|
|
159
169
|
*
|
|
160
170
|
* @internal
|
|
161
171
|
*/
|
|
162
|
-
async
|
|
172
|
+
async _fetch(endpoint, options = {}) {
|
|
173
|
+
if (this._closed) {
|
|
174
|
+
throw new ContextError("Client has been closed");
|
|
175
|
+
}
|
|
163
176
|
const url = `${this.baseUrl}${endpoint}`;
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
if (!response.ok) {
|
|
173
|
-
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
174
|
-
let errorCode;
|
|
175
|
-
let helpUrl;
|
|
177
|
+
const maxRetries = 3;
|
|
178
|
+
const timeoutMs = 3e4;
|
|
179
|
+
let lastError;
|
|
180
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
181
|
+
const controller = new AbortController();
|
|
182
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
176
183
|
try {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
const response = await fetch(url, {
|
|
185
|
+
...options,
|
|
186
|
+
signal: controller.signal,
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
190
|
+
...options.headers
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
clearTimeout(timeout);
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
196
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
197
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
201
|
+
let errorCode;
|
|
202
|
+
let helpUrl;
|
|
203
|
+
try {
|
|
204
|
+
const errorBody = await response.json();
|
|
205
|
+
if (errorBody.error) {
|
|
206
|
+
errorMessage = errorBody.error;
|
|
207
|
+
errorCode = errorBody.code;
|
|
208
|
+
helpUrl = errorBody.helpUrl;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
213
|
+
}
|
|
214
|
+
return response.json();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
clearTimeout(timeout);
|
|
217
|
+
if (error instanceof ContextError) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
221
|
+
const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
|
|
222
|
+
if (isRetryable && attempt < maxRetries) {
|
|
223
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
225
|
+
continue;
|
|
182
226
|
}
|
|
183
|
-
|
|
227
|
+
if (lastError.name === "AbortError") {
|
|
228
|
+
throw new ContextError(
|
|
229
|
+
`Request timed out after ${timeoutMs / 1e3}s`,
|
|
230
|
+
void 0,
|
|
231
|
+
408
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
throw new ContextError(
|
|
235
|
+
lastError.message,
|
|
236
|
+
void 0,
|
|
237
|
+
void 0
|
|
238
|
+
);
|
|
184
239
|
}
|
|
185
|
-
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
186
240
|
}
|
|
187
|
-
|
|
241
|
+
throw lastError ?? new ContextError("Request failed after retries");
|
|
188
242
|
}
|
|
189
243
|
};
|
|
190
244
|
|
|
191
245
|
// src/context/index.ts
|
|
192
246
|
var CONTEXT_REQUIREMENTS_KEY = "x-context-requirements";
|
|
247
|
+
var META_CONTEXT_REQUIREMENTS_KEY = "contextRequirements";
|
|
193
248
|
var CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
194
249
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu
|
|
195
250
|
chJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN
|
|
@@ -199,6 +254,41 @@ lfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep
|
|
|
199
254
|
weipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3
|
|
200
255
|
TQIDAQAB
|
|
201
256
|
-----END PUBLIC KEY-----`;
|
|
257
|
+
var JWKS_URL = "https://ctxprotocol.com/.well-known/jwks.json";
|
|
258
|
+
var KEY_CACHE_TTL_MS = 36e5;
|
|
259
|
+
var cachedPublicKey = null;
|
|
260
|
+
var cacheTimestamp = 0;
|
|
261
|
+
async function getPlatformPublicKey() {
|
|
262
|
+
const now = Date.now();
|
|
263
|
+
if (cachedPublicKey && now - cacheTimestamp < KEY_CACHE_TTL_MS) {
|
|
264
|
+
return cachedPublicKey;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const controller = new AbortController();
|
|
268
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
269
|
+
const response = await fetch(JWKS_URL, { signal: controller.signal });
|
|
270
|
+
clearTimeout(timeout);
|
|
271
|
+
if (response.ok) {
|
|
272
|
+
const jwks = await response.json();
|
|
273
|
+
if (jwks.keys && jwks.keys.length > 0) {
|
|
274
|
+
const key = jwks.keys[0];
|
|
275
|
+
if (key.x5c && key.x5c.length > 0) {
|
|
276
|
+
const pem = `-----BEGIN CERTIFICATE-----
|
|
277
|
+
${key.x5c[0]}
|
|
278
|
+
-----END CERTIFICATE-----`;
|
|
279
|
+
const { importX509 } = await import('jose');
|
|
280
|
+
cachedPublicKey = await importX509(pem, "RS256");
|
|
281
|
+
cacheTimestamp = now;
|
|
282
|
+
return cachedPublicKey;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
cachedPublicKey = await jose.importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, "RS256");
|
|
289
|
+
cacheTimestamp = now;
|
|
290
|
+
return cachedPublicKey;
|
|
291
|
+
}
|
|
202
292
|
var PROTECTED_MCP_METHODS = /* @__PURE__ */ new Set([
|
|
203
293
|
"tools/call"
|
|
204
294
|
// Uncomment these if you want to protect resource/prompt access:
|
|
@@ -230,7 +320,7 @@ async function verifyContextRequest(options) {
|
|
|
230
320
|
}
|
|
231
321
|
const token = authorizationHeader.split(" ")[1];
|
|
232
322
|
try {
|
|
233
|
-
const publicKey = await
|
|
323
|
+
const publicKey = await getPlatformPublicKey();
|
|
234
324
|
const { payload } = await jose.jwtVerify(token, publicKey, {
|
|
235
325
|
issuer: "https://ctxprotocol.com",
|
|
236
326
|
audience
|
|
@@ -264,14 +354,73 @@ function createContextMiddleware(options = {}) {
|
|
|
264
354
|
};
|
|
265
355
|
}
|
|
266
356
|
|
|
357
|
+
// src/handshake/types.ts
|
|
358
|
+
function isHandshakeAction(value) {
|
|
359
|
+
return typeof value === "object" && value !== null && "_action" in value && (value._action === "signature_request" || value._action === "transaction_proposal" || value._action === "auth_required");
|
|
360
|
+
}
|
|
361
|
+
function isSignatureRequest(value) {
|
|
362
|
+
return isHandshakeAction(value) && value._action === "signature_request";
|
|
363
|
+
}
|
|
364
|
+
function isTransactionProposal(value) {
|
|
365
|
+
return isHandshakeAction(value) && value._action === "transaction_proposal";
|
|
366
|
+
}
|
|
367
|
+
function isAuthRequired(value) {
|
|
368
|
+
return isHandshakeAction(value) && value._action === "auth_required";
|
|
369
|
+
}
|
|
370
|
+
function createSignatureRequest(params) {
|
|
371
|
+
return {
|
|
372
|
+
_action: "signature_request",
|
|
373
|
+
...params
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function createTransactionProposal(params) {
|
|
377
|
+
return {
|
|
378
|
+
_action: "transaction_proposal",
|
|
379
|
+
...params
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function createAuthRequired(params) {
|
|
383
|
+
return {
|
|
384
|
+
_action: "auth_required",
|
|
385
|
+
...params
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function wrapHandshakeResponse(action) {
|
|
389
|
+
const actionType = action._action.replace("_", " ");
|
|
390
|
+
return {
|
|
391
|
+
content: [
|
|
392
|
+
{
|
|
393
|
+
type: "text",
|
|
394
|
+
text: `Handshake required: ${actionType}. Please approve in the Context app.`
|
|
395
|
+
}
|
|
396
|
+
],
|
|
397
|
+
structuredContent: {
|
|
398
|
+
_meta: {
|
|
399
|
+
handshakeAction: action
|
|
400
|
+
},
|
|
401
|
+
status: "handshake_required",
|
|
402
|
+
message: action.meta?.description ?? `${actionType} required`
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
267
407
|
exports.CONTEXT_REQUIREMENTS_KEY = CONTEXT_REQUIREMENTS_KEY;
|
|
268
408
|
exports.ContextClient = ContextClient;
|
|
269
409
|
exports.ContextError = ContextError;
|
|
270
410
|
exports.Discovery = Discovery;
|
|
411
|
+
exports.META_CONTEXT_REQUIREMENTS_KEY = META_CONTEXT_REQUIREMENTS_KEY;
|
|
271
412
|
exports.Tools = Tools;
|
|
413
|
+
exports.createAuthRequired = createAuthRequired;
|
|
272
414
|
exports.createContextMiddleware = createContextMiddleware;
|
|
415
|
+
exports.createSignatureRequest = createSignatureRequest;
|
|
416
|
+
exports.createTransactionProposal = createTransactionProposal;
|
|
417
|
+
exports.isAuthRequired = isAuthRequired;
|
|
418
|
+
exports.isHandshakeAction = isHandshakeAction;
|
|
273
419
|
exports.isOpenMcpMethod = isOpenMcpMethod;
|
|
274
420
|
exports.isProtectedMcpMethod = isProtectedMcpMethod;
|
|
421
|
+
exports.isSignatureRequest = isSignatureRequest;
|
|
422
|
+
exports.isTransactionProposal = isTransactionProposal;
|
|
275
423
|
exports.verifyContextRequest = verifyContextRequest;
|
|
424
|
+
exports.wrapHandshakeResponse = wrapHandshakeResponse;
|
|
276
425
|
//# sourceMappingURL=index.cjs.map
|
|
277
426
|
//# sourceMappingURL=index.cjs.map
|