From 00ac417fb1e24d0a3d8dbf5023fbf9c53d6ad6c7 Mon Sep 17 00:00:00 2001 From: John Pope Date: Sun, 6 Jul 2025 14:46:53 +1000 Subject: [PATCH 1/4] mock api --- .gitignore | 4 +- CLAUDE.md | 104 ++ README-mock.md | 164 +++ examples/mock-example.ts | 114 ++ mock-server/server.ts | 315 ++++++ package.json | 6 + pnpm-lock.yaml | 2263 ++++++++++++++++++++++++++++++++++++++ src/grokApi.ts | 2 +- src/index.ts | 1 + src/mockGrokApi.ts | 301 +++++ 10 files changed, 3272 insertions(+), 2 deletions(-) create mode 100644 CLAUDE.md create mode 100644 README-mock.md create mode 100644 examples/mock-example.ts create mode 100644 mock-server/server.ts create mode 100644 pnpm-lock.yaml create mode 100644 src/mockGrokApi.ts diff --git a/.gitignore b/.gitignore index 7c3f300..0e8e375 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ dist/ .env -grok-cookies.json \ No newline at end of file +grok-cookies.json +.chrome-data/ +.vscode/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1feec11 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,104 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a TypeScript client library for interacting with Grok AI (`grok-api-ts`). The library provides automated login functionality, cookie management, and comprehensive interaction with Grok's API including streaming responses. + +## Common Commands + +**Build the project:** +```bash +npm run build +# or +pnpm run build +``` + +**Development (run with ts-node):** +```bash +npm run dev +# or +pnpm run dev +``` + +**Clean build artifacts:** +```bash +npm run clean +# or +pnpm run clean +``` + +**Run example:** +```bash +npm run example +# or +pnpm run example +``` + +**Prepare for publishing:** +```bash +npm run prepublishOnly +# or +pnpm run prepublishOnly +``` + +## Architecture + +### Core Components + +- **`src/grokApi.ts`**: Main GrokAPI class containing all functionality +- **`src/index.ts`**: Entry point that exports the GrokAPI class and types +- **`examples/`**: Example usage files showing different API patterns + +### Key Features + +1. **Authentication System**: Uses patchright (patched Playwright) to automate browser-based login to X.AI +2. **Cookie Management**: Persistent cookie storage in `grok-cookies.json` to avoid repeated logins +3. **Streaming Support**: Real-time response streaming with token-by-token callbacks +4. **Conversation Management**: Maintains conversation state and supports follow-up messages + +### Main API Methods + +- `login(username, password)`: Automated browser login +- `sendMessage(options)`: Send message to Grok with customizable options +- `continueConversation(message)`: Continue existing conversation +- `sendMessageStream(options, callbacks)`: Streaming version with real-time callbacks +- `ensureAuthenticated()`: Checks/handles authentication state + +### Key Data Structures + +- `GrokSendMessageOptions`: Message configuration including search, image generation, custom instructions +- `ParsedGrokResponse`: Parsed response containing message, metadata, and conversation info +- `GrokStreamCallbacks`: Streaming callbacks for tokens, completion, and errors + +## Dependencies + +- **Runtime**: `got-scraping`, `patchright`, `tough-cookie` +- **Development**: TypeScript, ts-node, rimraf, puppeteer +- **Peer**: `chrome-launcher` (optional) + +## Testing + +Currently no test suite is configured (`"test": "echo \"No tests yet\"`). When implementing tests, consider testing: +- Authentication flow +- Message sending/receiving +- Cookie persistence +- Error handling +- Streaming functionality + +## File Structure + +- `src/`: TypeScript source files +- `dist/`: Compiled JavaScript output +- `examples/`: Usage examples +- `node_modules/`: Dependencies +- `.chrome-data/`: Browser profile data for authentication + +## Important Notes + +- Chrome browser is required for authentication (uses patchright) +- Cookies are stored in `grok-cookies.json` for persistence +- The library targets Node.js 14+ with ES2020 +- Uses CommonJS modules for compatibility +- Includes TypeScript declarations for full type safety \ No newline at end of file diff --git a/README-mock.md b/README-mock.md new file mode 100644 index 0000000..5c48254 --- /dev/null +++ b/README-mock.md @@ -0,0 +1,164 @@ +# Mock Grok API Server + +This project includes a mock server that simulates the Grok API endpoints for development and testing purposes. The mock server eliminates the need for authentication and provides realistic streaming responses. + +## Quick Start + +1. **Install dependencies:** +```bash +npm install +# or +pnpm install +``` + +2. **Start the mock server:** +```bash +npm run mock-server +# or +pnpm run mock-server +``` + +3. **Run the mock example:** +```bash +# In another terminal +npm run mock-example +# or +pnpm run mock-example +``` + +## Mock Server Features + +- **No Authentication Required**: Skip the complex browser-based login process +- **Realistic Streaming**: Simulates token-by-token response streaming +- **Conversation Management**: Maintains conversation state like the real API +- **Follow-up Suggestions**: Provides mock follow-up suggestions +- **Error Handling**: Proper error responses and status codes +- **CORS Support**: Configured for cross-origin requests + +## Usage + +### Using MockGrokAPI Class + +```typescript +import { MockGrokAPI } from 'grok-api-ts'; + +const mockGrokApi = new MockGrokAPI('http://localhost:3001', true); + +// Check if mock server is running +const isRunning = await mockGrokApi.checkMockServer(); + +// Send message (no authentication needed) +const response = await mockGrokApi.sendMessage({ + message: "Hello, mock Grok!" +}); + +console.log(response.fullMessage); +``` + +### Direct API Calls + +You can also make direct HTTP requests to the mock server: + +```bash +# New conversation +curl -X POST http://localhost:3001/rest/app-chat/conversations/new \ + -H "Content-Type: application/json" \ + -d '{"message": "Hello!", "modelName": "grok-3"}' + +# Continue conversation +curl -X POST http://localhost:3001/rest/app-chat/conversations/conv_1/responses \ + -H "Content-Type: application/json" \ + -d '{"message": "Follow up", "parentResponseId": "resp_1"}' + +# Health check +curl http://localhost:3001/health +``` + +## Mock Server Endpoints + +- `POST /rest/app-chat/conversations/new` - Create new conversation +- `POST /rest/app-chat/conversations/:id/responses` - Continue conversation +- `GET /health` - Health check endpoint + +## Configuration + +The mock server runs on port 3001 by default. You can customize the port: + +```bash +PORT=3002 npm run mock-server +``` + +When using MockGrokAPI, specify the custom URL: + +```typescript +const mockGrokApi = new MockGrokAPI('http://localhost:3002', true); +``` + +## Mock Response Format + +The mock server returns responses in the same format as the real Grok API: + +```json +{ + "result": { + "conversation": { + "conversationId": "conv_1", + "title": "Mock Conversation 1" + }, + "response": { + "token": "Hello ", + "responseId": "resp_1" + }, + "finalMetadata": { + "followUpSuggestions": ["Follow up 1", "Follow up 2"], + "feedbackLabels": ["helpful", "accurate"] + } + } +} +``` + +## Development Benefits + +1. **Faster Development**: No need to wait for real API authentication +2. **Offline Testing**: Work without internet connection +3. **Predictable Responses**: Consistent mock responses for testing +4. **Rate Limit Free**: No API rate limits during development +5. **Easy Debugging**: Full control over response timing and content + +## Switching Between Real and Mock APIs + +```typescript +// For development/testing +const grokApi = new MockGrokAPI('http://localhost:3001', true); + +// For production +const grokApi = new GrokAPI(); +await grokApi.login('username', 'password'); +``` + +## Custom Mock Responses + +You can modify `mock-server/server.ts` to customize: + +- Response content and variety +- Streaming timing and behavior +- Error simulation +- Follow-up suggestions +- Conversation titles + +## Troubleshooting + +**Mock server won't start:** +- Check if port 3001 is available +- Install dependencies: `npm install` +- Check for TypeScript compilation errors + +**Connection refused errors:** +- Ensure mock server is running: `npm run mock-server` +- Verify the correct port and URL +- Check firewall settings + +**Streaming not working:** +- Ensure you're using the streaming methods correctly +- Check console for error messages +- Verify callback functions are properly defined \ No newline at end of file diff --git a/examples/mock-example.ts b/examples/mock-example.ts new file mode 100644 index 0000000..be49581 --- /dev/null +++ b/examples/mock-example.ts @@ -0,0 +1,114 @@ +import { MockGrokAPI } from '../src/mockGrokApi'; + +async function main() { + try { + console.log('šŸš€ Starting Mock Grok API Example'); + console.log('Make sure to run the mock server first: npm run mock-server'); + console.log(''); + + // Initialize the Mock Grok API + const mockGrokApi = new MockGrokAPI('http://localhost:3001', true); + + // Check if mock server is running + const isServerRunning = await mockGrokApi.checkMockServer(); + if (!isServerRunning) { + console.error('āŒ Mock server is not running!'); + console.log('Please run: npm run mock-server'); + process.exit(1); + } + + console.log('āœ… Mock server is running'); + console.log(''); + + // Example 1: Send a message to create a new conversation + console.log('šŸ“ Example 1: Creating new conversation'); + console.log('Sending message: "What are the three laws of robotics?"'); + const response1 = await mockGrokApi.sendMessage({ + message: "What are the three laws of robotics?", + }); + + console.log('\nšŸ¤– Mock Grok Response:'); + console.log('----------------------------------------'); + console.log(response1.fullMessage); + console.log('----------------------------------------'); + + if (response1.metadata?.followUpSuggestions) { + console.log('\nšŸ’” Follow-up suggestions:'); + response1.metadata.followUpSuggestions.forEach((suggestion, index) => { + console.log(`${index + 1}. ${suggestion}`); + }); + } + + // Example 2: Continue the conversation + console.log('\nšŸ“ Example 2: Continuing conversation'); + console.log('Sending follow-up: "Who created these laws?"'); + const response2 = await mockGrokApi.continueConversation( + "Who created these laws?" + ); + + console.log('\nšŸ¤– Mock Grok Follow-up Response:'); + console.log('----------------------------------------'); + console.log(response2.fullMessage); + console.log('----------------------------------------'); + + // Example 3: Streaming response + console.log('\nšŸ“ Example 3: Streaming response'); + console.log('Sending streaming message: "Tell me about space exploration"'); + console.log('\n🌊 Streaming response:'); + + await mockGrokApi.sendMessageStream( + { + message: "Tell me about space exploration", + }, + { + onToken: (token: string) => { + process.stdout.write(token); + }, + onComplete: (response) => { + console.log('\n\nāœ… Streaming completed!'); + console.log(`Response ID: ${response.responseId}`); + console.log(`Conversation ID: ${response.conversationId}`); + + if (response.metadata?.followUpSuggestions) { + console.log('\nšŸ’” Follow-up suggestions:'); + response.metadata.followUpSuggestions.forEach((suggestion, index) => { + console.log(`${index + 1}. ${suggestion}`); + }); + } + }, + onError: (error) => { + console.error('\nāŒ Streaming error:', error); + } + } + ); + + // Example 4: Message with custom options + console.log('\nšŸ“ Example 4: Message with custom options'); + const response4 = await mockGrokApi.sendMessage({ + message: "Explain quantum computing", + customInstructions: "Keep it simple and use analogies", + forceConcise: true, + disableSearch: true, + enableImageGeneration: false + }); + + console.log('\nšŸ¤– Mock Grok Response with Custom Options:'); + console.log('----------------------------------------'); + console.log(response4.fullMessage); + console.log('----------------------------------------'); + + // Show conversation info + const conversationInfo = mockGrokApi.getConversationInfo(); + console.log('\nšŸ“Š Final Conversation Info:'); + console.log(`Conversation ID: ${conversationInfo.conversationId}`); + console.log(`Last Response ID: ${conversationInfo.lastResponseId}`); + + console.log('\nšŸŽ‰ Mock example completed successfully!'); + + } catch (error) { + console.error('āŒ Error:', error); + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/mock-server/server.ts b/mock-server/server.ts new file mode 100644 index 0000000..52c6543 --- /dev/null +++ b/mock-server/server.ts @@ -0,0 +1,315 @@ +import express from 'express'; +import cors from 'cors'; +import { Request, Response } from 'express'; + +const app = express(); +const PORT = process.env.PORT || 3001; + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Mock conversation storage +const conversations = new Map(); +let conversationCounter = 1; +let responseCounter = 1; + +// Mock responses pool +const mockResponses = [ + "I'm a mock Grok AI assistant. I can help you with various questions and tasks. What would you like to know?", + "That's an interesting question! As a mock assistant, I'm designed to simulate responses to help with development and testing.", + "I understand you're looking for information on that topic. While I'm just a mock endpoint, I can provide simulated responses.", + "Thanks for your question! This is a simulated response from the mock Grok backend.", + "I'm here to help! This mock server is designed to simulate the real Grok API for development purposes." +]; + +// Generate mock streaming response +function generateStreamingResponse(message: string): string[] { + const baseResponse = mockResponses[Math.floor(Math.random() * mockResponses.length)]; + const customResponse = `You asked: "${message}". ${baseResponse}`; + + // Split into chunks for streaming simulation + const words = customResponse.split(' '); + const chunks: string[] = []; + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + if (i === words.length - 1) { + chunks.push(word); + } else { + chunks.push(word + ' '); + } + } + + return chunks; +} + +// Mock endpoint for new conversations +app.post('/rest/app-chat/conversations/new', (req: Request, res: Response) => { + const { message, modelName = 'grok-3', temporary = false } = req.body; + + const conversationId = `conv_${conversationCounter++}`; + const responseId = `resp_${responseCounter++}`; + const userResponseId = `user_${responseCounter++}`; + + console.log(`Mock: New conversation ${conversationId} - Message: "${message}"`); + + // Store conversation + conversations.set(conversationId, { + id: conversationId, + title: `Mock Conversation ${conversationCounter - 1}`, + messages: [ + { id: userResponseId, message, sender: 'user', timestamp: new Date().toISOString() } + ] + }); + + const chunks = generateStreamingResponse(message); + + // Send response as streaming JSON lines + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Transfer-Encoding': 'chunked', + 'Access-Control-Allow-Origin': '*' + }); + + // First, send conversation creation + res.write(JSON.stringify({ + result: { + conversation: { + conversationId, + title: `Mock Conversation ${conversationCounter - 1}`, + starred: false, + createTime: new Date().toISOString(), + modifyTime: new Date().toISOString(), + systemPromptName: "default", + temporary: false, + mediaTypes: [] + } + } + }) + '\n'); + + // Send user response confirmation + res.write(JSON.stringify({ + result: { + userResponse: { + responseId: userResponseId, + message, + sender: "user", + createTime: new Date().toISOString(), + parentResponseId: "", + manual: true, + partial: false, + shared: false, + query: message, + queryType: "text" + } + } + }) + '\n'); + + // Stream response tokens + let tokenIndex = 0; + const streamTokens = () => { + if (tokenIndex < chunks.length) { + const isLast = tokenIndex === chunks.length - 1; + + res.write(JSON.stringify({ + result: { + response: { + token: chunks[tokenIndex], + isThinking: false, + isSoftStop: false, + responseId: responseId + } + } + }) + '\n'); + + tokenIndex++; + + if (isLast) { + // Send final metadata + setTimeout(() => { + res.write(JSON.stringify({ + result: { + response: { + finalMetadata: { + followUpSuggestions: [ + "Can you tell me more about this topic?", + "What are the key points I should remember?", + "How does this relate to other concepts?" + ], + feedbackLabels: ["helpful", "accurate", "clear"], + toolsUsed: {} + }, + modelResponse: { + responseId: responseId, + message: chunks.join(''), + sender: "assistant", + createTime: new Date().toISOString(), + parentResponseId: userResponseId, + manual: false, + partial: false, + shared: false, + query: message, + queryType: "text", + webSearchResults: [], + xpostIds: [], + xposts: [], + generatedImageUrls: [], + imageAttachments: [], + fileAttachments: [], + cardAttachmentsJson: [], + fileUris: [], + fileAttachmentsMetadata: [], + isControl: false, + steps: [], + mediaTypes: [] + } + } + } + }) + '\n'); + + // Send title update + res.write(JSON.stringify({ + result: { + title: { + newTitle: `Mock: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}` + } + } + }) + '\n'); + + res.end(); + }, 100); + } else { + setTimeout(streamTokens, 50 + Math.random() * 100); // Simulate realistic timing + } + } + }; + + setTimeout(streamTokens, 200); // Initial delay +}); + +// Mock endpoint for continuing conversations +app.post('/rest/app-chat/conversations/:conversationId/responses', (req: Request, res: Response) => { + const { conversationId } = req.params; + const { message, parentResponseId } = req.body; + + console.log(`Mock: Continue conversation ${conversationId} - Message: "${message}"`); + + const responseId = `resp_${responseCounter++}`; + const userResponseId = `user_${responseCounter++}`; + + const chunks = generateStreamingResponse(message); + + // Send response as streaming JSON lines + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Transfer-Encoding': 'chunked', + 'Access-Control-Allow-Origin': '*' + }); + + // Send user response confirmation + res.write(JSON.stringify({ + result: { + userResponse: { + responseId: userResponseId, + message, + sender: "user", + createTime: new Date().toISOString(), + parentResponseId: parentResponseId || "", + manual: true, + partial: false, + shared: false, + query: message, + queryType: "text" + } + } + }) + '\n'); + + // Stream response tokens + let tokenIndex = 0; + const streamTokens = () => { + if (tokenIndex < chunks.length) { + const isLast = tokenIndex === chunks.length - 1; + + res.write(JSON.stringify({ + result: { + response: { + token: chunks[tokenIndex], + isThinking: false, + isSoftStop: false, + responseId: responseId + } + } + }) + '\n'); + + tokenIndex++; + + if (isLast) { + // Send final metadata + setTimeout(() => { + res.write(JSON.stringify({ + result: { + response: { + finalMetadata: { + followUpSuggestions: [ + "What else would you like to know?", + "Can you elaborate on that?", + "Are there any related topics?" + ], + feedbackLabels: ["helpful", "accurate", "clear"], + toolsUsed: {} + }, + modelResponse: { + responseId: responseId, + message: chunks.join(''), + sender: "assistant", + createTime: new Date().toISOString(), + parentResponseId: userResponseId, + manual: false, + partial: false, + shared: false, + query: message, + queryType: "text", + webSearchResults: [], + xpostIds: [], + xposts: [], + generatedImageUrls: [], + imageAttachments: [], + fileAttachments: [], + cardAttachmentsJson: [], + fileUris: [], + fileAttachmentsMetadata: [], + isControl: false, + steps: [], + mediaTypes: [] + } + } + } + }) + '\n'); + + res.end(); + }, 100); + } else { + setTimeout(streamTokens, 50 + Math.random() * 100); + } + } + }; + + setTimeout(streamTokens, 200); +}); + +// Health check endpoint +app.get('/health', (req: Request, res: Response) => { + res.json({ status: 'ok', message: 'Mock Grok API server is running' }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`Mock Grok API server running on port ${PORT}`); + console.log(`Health check: http://localhost:${PORT}/health`); + console.log('Endpoints:'); + console.log(` POST http://localhost:${PORT}/rest/app-chat/conversations/new`); + console.log(` POST http://localhost:${PORT}/rest/app-chat/conversations/:id/responses`); +}); + +export default app; \ No newline at end of file diff --git a/package.json b/package.json index b1ba96e..9045104 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "dev": "ts-node src/index.ts", "clean": "rimraf dist", "example": "ts-node examples/test-streaming.ts", + "mock-server": "ts-node mock-server/server.ts", + "mock-example": "ts-node examples/mock-example.ts", "prepublishOnly": "npm run clean && npm run build", "test": "echo \"No tests yet\"" @@ -45,8 +47,12 @@ "tough-cookie": "^4.1.3" }, "devDependencies": { + "@types/express": "^4.17.21", + "@types/cors": "^2.8.17", "@types/node": "^20.10.5", "@types/tough-cookie": "^4.0.5", + "cors": "^2.8.5", + "express": "^4.18.2", "puppeteer": "^24.3.0", "rimraf": "^5.0.10", "ts-node": "^10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..33f3ae4 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2263 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + chrome-launcher: + specifier: ^0.15.2 + version: 0.15.2 + got-scraping: + specifier: ^3.2.15 + version: 3.2.15 + patchright: + specifier: ^1.50.1 + version: 1.52.5 + tough-cookie: + specifier: ^4.1.3 + version: 4.1.4 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + '@types/node': + specifier: ^20.10.5 + version: 20.19.4 + '@types/tough-cookie': + specifier: ^4.0.5 + version: 4.0.5 + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^4.18.2 + version: 4.21.2 + puppeteer: + specifier: ^24.3.0 + version: 24.11.2(typescript@5.8.3) + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.4)(typescript@5.8.3) + typescript: + specifier: ^5.3.3 + version: 5.8.3 + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@puppeteer/browsers@2.10.5': + resolution: {integrity: sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==} + engines: {node: '>=18'} + hasBin: true + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@20.19.4': + resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/responselike@1.0.0': + resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + + bare-fs@4.1.6: + resolution: {integrity: sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacheable-lookup@6.1.0: + resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.2: + resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001726: + resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-bidi@5.1.0: + resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} + peerDependencies: + devtools-protocol: '*' + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + devtools-protocol@0.0.1464554: + resolution: {integrity: sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.179: + resolution: {integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generative-bayesian-network@2.1.69: + resolution: {integrity: sha512-k8GgdPT9oCRchU4+7ofh/qpsmvSaOI0znFt/edanyWBBxLjSnjoSU97C15fGqQSdJIZ9uwsCU0RO8xnpLEX95w==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-uri@6.0.4: + resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} + engines: {node: '>= 14'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got-cjs@12.5.4: + resolution: {integrity: sha512-Uas6lAsP8bRCt5WXGMhjFf/qEHTrm4v4qxGR02rLG2kdG9qedctvlkdwXVcDJ7Cs84X+r4dPU7vdwGjCaspXug==} + engines: {node: '>=12'} + + got-scraping@3.2.15: + resolution: {integrity: sha512-EXqDe4JVyrWZTHhPT1G98+wv+oozDpUHpCLVd6C/HpNtySh6xSSaIgLUCBpAgIT5xheq6ZgNkgb0rA97pq8vZA==} + engines: {node: '>=15.10.0'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + header-generator@2.1.69: + resolution: {integrity: sha512-J3BK8UtPAR1Lvvfd/qlzmAS1Qb6Q2qx4K1s4FjYVrYvSQnUc7GgOAo9uacebg6WGCdP0eWxtojh7JwEd+AD2Hw==} + engines: {node: '>=16.0.0'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + ow@0.28.2: + resolution: {integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==} + engines: {node: '>=12'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + patchright-core@1.52.5: + resolution: {integrity: sha512-8rnLVEK9jDZWzFPy2hCQrp4xhU7zgE8IqseZyjGkgxf+jpAWTuGNgIAlcsKZMfQrDL8j1mZgRIGNAQT00nk6QA==} + engines: {node: '>=18'} + hasBin: true + + patchright@1.52.5: + resolution: {integrity: sha512-wmRpsF9n02j0S+YDk0U3ouuWipHbUowwxbf/4K4G9ng311vvugoo8WndbU/fCsGtme8gYNfcEGcpfF6/L1NXHg==} + engines: {node: '>=18'} + hasBin: true + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + puppeteer-core@24.11.2: + resolution: {integrity: sha512-c49WifNb8hix+gQH17TldmD6TC/Md2HBaTJLHexIUq4sZvo2pyHY/Pp25qFQjibksBu/SJRYUY7JsoaepNbiRA==} + engines: {node: '>=18'} + + puppeteer@24.11.2: + resolution: {integrity: sha512-HopdRZWHa5zk0HSwd8hU+GlahQ3fmesTAqMIDHVY9HasCvppcYuHYXyjml0nlm+nbwVCqAQWV+dSmiNCrZGTGQ==} + engines: {node: '>=18'} + hasBin: true + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.5: + resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + vali-date@1.0.0: + resolution: {integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==} + engines: {node: '>=0.10.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + zod@3.25.74: + resolution: {integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.27.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@puppeteer/browsers@2.10.5': + dependencies: + debug: 4.4.1 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.2 + tar-fs: 3.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + + '@sindresorhus/is@4.6.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.4 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.4 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.19.4 + + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 20.19.4 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/node@20.19.4': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/responselike@1.0.0': + dependencies: + '@types/node': 20.19.4 + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.4 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.4 + '@types/send': 0.17.5 + + '@types/tough-cookie@4.0.5': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.4 + optional: true + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + adm-zip@0.5.16: {} + + agent-base@7.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + arg@4.1.3: {} + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + b4a@1.6.7: {} + + balanced-match@1.0.2: {} + + bare-events@2.5.4: + optional: true + + bare-fs@4.1.6: + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + optional: true + + bare-os@3.6.1: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.1 + optional: true + + bare-stream@2.6.5(bare-events@2.5.4): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.5.4 + optional: true + + basic-ftp@5.0.5: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001726 + electron-to-chromium: 1.5.179 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + buffer-crc32@0.2.13: {} + + bytes@3.1.2: {} + + cacheable-lookup@6.1.0: {} + + cacheable-request@7.0.2: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001726: {} + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 20.19.4 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-bidi@5.1.0(devtools-protocol@0.0.1464554): + dependencies: + devtools-protocol: 0.0.1464554 + mitt: 3.0.1 + zod: 3.25.74 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.0(typescript@5.8.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.8.3 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@6.0.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + defer-to-connect@2.0.1: {} + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + devtools-protocol@0.0.1464554: {} + + diff@4.0.2: {} + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.179: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extract-zip@2.0.1: + dependencies: + debug: 4.4.1 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-fifo@1.3.2: {} + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@1.7.2: {} + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fsevents@2.3.2: + optional: true + + function-bind@1.1.2: {} + + generative-bayesian-network@2.1.69: + dependencies: + adm-zip: 0.5.16 + tslib: 2.8.1 + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@6.0.1: {} + + get-uri@6.0.4: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + gopd@1.2.0: {} + + got-cjs@12.5.4: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/responselike': 1.0.0 + cacheable-lookup: 6.1.0 + cacheable-request: 7.0.2 + decompress-response: 6.0.0 + form-data-encoder: 1.7.2 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + got-scraping@3.2.15: + dependencies: + got-cjs: 12.5.4 + header-generator: 2.1.69 + http2-wrapper: 2.2.1 + mimic-response: 3.1.0 + ow: 0.28.2 + quick-lru: 5.1.1 + tslib: 2.8.1 + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + header-generator@2.1.69: + dependencies: + browserslist: 4.25.1 + generative-bayesian-network: 2.1.69 + ow: 0.28.2 + tslib: 2.8.1 + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + inherits@2.0.4: {} + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-docker@2.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-obj@2.0.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@1.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lines-and-columns@1.2.4: {} + + lodash.isequal@4.5.0: {} + + lowercase-keys@2.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@7.18.3: {} + + make-error@1.3.6: {} + + marky@1.3.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + mitt@3.0.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@0.6.3: {} + + netmask@2.0.2: {} + + node-releases@2.0.19: {} + + normalize-url@6.1.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + ow@0.28.2: + dependencies: + '@sindresorhus/is': 4.6.0 + callsites: 3.1.0 + dot-prop: 6.0.1 + lodash.isequal: 4.5.0 + vali-date: 1.0.0 + + p-cancelable@2.1.1: {} + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.1 + get-uri: 6.0.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + patchright-core@1.52.5: {} + + patchright@1.52.5: + dependencies: + patchright-core: 1.52.5 + optionalDependencies: + fsevents: 2.3.2 + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.12: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + progress@2.0.3: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + puppeteer-core@24.11.2: + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1464554) + debug: 4.4.1 + devtools-protocol: 0.0.1464554 + typed-query-selector: 2.12.0 + ws: 8.18.3 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + puppeteer@24.11.2(typescript@5.8.3): + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1464554) + cosmiconfig: 9.0.0(typescript@5.8.3) + devtools-protocol: 0.0.1464554 + puppeteer-core: 24.11.2 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - typescript + - utf-8-validate + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + querystringify@2.2.0: {} + + quick-lru@5.1.1: {} + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + + resolve-alpn@1.2.1: {} + + resolve-from@4.0.0: {} + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.7.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + socks: 2.8.5 + transitivePeerDependencies: + - supports-color + + socks@2.8.5: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + + source-map@0.6.1: + optional: true + + sprintf-js@1.1.3: {} + + statuses@2.0.1: {} + + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + tar-fs@3.1.0: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.6 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.1 + + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + + toidentifier@1.0.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.4 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@2.8.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-query-selector@2.12.0: {} + + typescript@5.8.3: {} + + undici-types@6.21.0: {} + + universalify@0.2.0: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + utils-merge@1.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + vali-date@1.0.0: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@8.18.3: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yn@3.1.1: {} + + zod@3.25.74: {} diff --git a/src/grokApi.ts b/src/grokApi.ts index cb6c096..83011b7 100644 --- a/src/grokApi.ts +++ b/src/grokApi.ts @@ -359,7 +359,7 @@ export class GrokAPI { }; } - private parseGrokResponse(responseBody: string): ParsedGrokResponse { + public parseGrokResponse(responseBody: string): ParsedGrokResponse { const jsonLines = responseBody.trim().split('\n'); let fullMessage = ''; diff --git a/src/index.ts b/src/index.ts index 614513f..b50a29c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from './grokApi'; +export * from './mockGrokApi'; export { GrokAPI as default } from './grokApi'; \ No newline at end of file diff --git a/src/mockGrokApi.ts b/src/mockGrokApi.ts new file mode 100644 index 0000000..d1ecf75 --- /dev/null +++ b/src/mockGrokApi.ts @@ -0,0 +1,301 @@ +import { GrokAPI, GrokSendMessageOptions, ParsedGrokResponse, GrokStreamCallbacks } from './grokApi'; +import { gotScraping } from 'got-scraping'; + +export class MockGrokAPI extends GrokAPI { + private mockBaseUrl: string; + private skipAuth: boolean; + + constructor(mockBaseUrl: string = 'http://localhost:3001', skipAuth: boolean = true) { + super([], ''); // Initialize with empty cookies + this.mockBaseUrl = mockBaseUrl; + this.skipAuth = skipAuth; + } + + // Override authentication methods for mock + isAuthenticated(): boolean { + return this.skipAuth || super.isAuthenticated(); + } + + async ensureAuthenticated(): Promise { + if (this.skipAuth) { + return true; + } + return super.ensureAuthenticated(); + } + + async login(username: string, password: string): Promise { + if (this.skipAuth) { + console.log('Mock mode: Skipping authentication'); + return true; + } + return super.login(username, password); + } + + async sendMessage(options: GrokSendMessageOptions): Promise { + if (!this.skipAuth && !this.isAuthenticated()) { + throw new Error('Authentication required: Please call login() method first or provide cookies'); + } + + try { + const { + message, + conversationId = 'new', + parentResponseId = '', + disableSearch = false, + enableImageGeneration = true, + customInstructions = "", + forceConcise = false, + imageAttachments = [], + isReasoning = false + } = options; + + const isNewConversation = conversationId === 'new'; + const url = isNewConversation + ? `${this.mockBaseUrl}/rest/app-chat/conversations/new` + : `${this.mockBaseUrl}/rest/app-chat/conversations/${conversationId}/responses`; + + console.log(`Mock: Sending message to ${isNewConversation ? 'new conversation' : 'existing conversation'}: ${conversationId}`); + + const response = await gotScraping.post(url, { + headers: { + 'accept': '*/*', + 'content-type': 'application/json', + }, + json: { + "temporary": false, + "modelName": "grok-3", + "message": message, + "fileAttachments": [], + "imageAttachments": imageAttachments, + "disableSearch": disableSearch, + "enableImageGeneration": enableImageGeneration, + "returnImageBytes": false, + "returnRawGrokInXaiRequest": false, + "enableImageStreaming": true, + "imageGenerationCount": 2, + "forceConcise": forceConcise, + "toolOverrides": {}, + "enableSideBySide": false, + "sendFinalMetadata": true, + "customInstructions": customInstructions, + "deepsearchPreset": "", + "isReasoning": isReasoning, + ...(isNewConversation ? {} : { "parentResponseId": parentResponseId }) + } + }); + + const parsedResponse = this.parseGrokResponse(response.body); + + if (parsedResponse.conversationId) { + (this as any).lastConversationId = parsedResponse.conversationId; + } + + if (parsedResponse.responseId) { + (this as any).lastResponseId = parsedResponse.responseId; + } + + return parsedResponse; + } catch (error: any) { + console.error('Error sending message to Mock Grok:', error); + throw error; + } + } + + async sendMessageStream(options: GrokSendMessageOptions, callbacks: GrokStreamCallbacks): Promise { + if (!this.skipAuth && !this.isAuthenticated()) { + throw new Error('Authentication required: Please call login() method first or provide cookies'); + } + + try { + const { + message, + conversationId = 'new', + parentResponseId = '', + disableSearch = false, + enableImageGeneration = true, + customInstructions = "", + forceConcise = false, + imageAttachments = [], + isReasoning = false + } = options; + + const isNewConversation = conversationId === 'new'; + const url = isNewConversation + ? `${this.mockBaseUrl}/rest/app-chat/conversations/new` + : `${this.mockBaseUrl}/rest/app-chat/conversations/${conversationId}/responses`; + + console.log(`Mock: Streaming message to ${isNewConversation ? 'new conversation' : 'existing conversation'}: ${conversationId}`); + + // Initialize accumulated response data + let fullMessage = ''; + let responseId: string | undefined; + let title: string | undefined; + let metadata: any; + let modelResponse: any; + let conversationId_: string | undefined; + let accumulator = ''; + + // Create a stream request + const stream = gotScraping.stream.post(url, { + headers: { + 'accept': '*/*', + 'content-type': 'application/json', + }, + json: { + "temporary": false, + "modelName": "grok-3", + "message": message, + "fileAttachments": [], + "imageAttachments": imageAttachments, + "disableSearch": disableSearch, + "enableImageGeneration": enableImageGeneration, + "returnImageBytes": false, + "returnRawGrokInXaiRequest": false, + "enableImageStreaming": true, + "imageGenerationCount": 2, + "forceConcise": forceConcise, + "toolOverrides": {}, + "enableSideBySide": false, + "sendFinalMetadata": true, + "customInstructions": customInstructions, + "deepsearchPreset": "", + "isReasoning": isReasoning, + ...(isNewConversation ? {} : { "parentResponseId": parentResponseId }) + } + }); + + // Process the stream data (same logic as parent class) + stream.on('data', (chunk: Buffer) => { + const chunkStr = chunk.toString(); + accumulator += chunkStr; + + // Process complete JSON lines + let lineEnd = accumulator.indexOf('\n'); + while (lineEnd !== -1) { + const line = accumulator.substring(0, lineEnd); + accumulator = accumulator.substring(lineEnd + 1); + + try { + if (line.trim()) { + const parsedLine: any = JSON.parse(line); + + // Handle conversation ID + if (parsedLine.result?.conversation?.conversationId) { + conversationId_ = parsedLine.result.conversation.conversationId; + console.log(`Mock: Found conversation ID: ${conversationId_}`); + } + + // Handle token and send it via callback + let token: string | undefined; + + if (parsedLine.result?.response?.token !== undefined) { + token = parsedLine.result.response.token; + responseId = parsedLine.result.response.responseId; + } else if (parsedLine.result?.token !== undefined) { + token = parsedLine.result.token || ''; + responseId = parsedLine.result.responseId; + } + + if (token !== undefined) { + fullMessage += token; + if (callbacks.onToken) { + callbacks.onToken(token); + } + } + + // Handle metadata + if (parsedLine.result?.response?.finalMetadata) { + metadata = parsedLine.result.response.finalMetadata; + } else if (parsedLine.result?.finalMetadata) { + metadata = parsedLine.result.finalMetadata; + } + + // Handle model response + if (parsedLine.result?.response?.modelResponse) { + modelResponse = parsedLine.result.response.modelResponse; + responseId = modelResponse.responseId; + } else if (parsedLine.result?.modelResponse) { + modelResponse = parsedLine.result.modelResponse; + responseId = modelResponse.responseId; + } + + // Handle title + if (parsedLine.result?.title) { + title = parsedLine.result.title.newTitle; + } + + // Handle user response + if (parsedLine.result?.response?.userResponse?.responseId) { + responseId = parsedLine.result.response.userResponse.responseId; + } else if (parsedLine.result?.userResponse?.responseId) { + responseId = parsedLine.result.userResponse.responseId; + } + } + } catch (error) { + console.error('Error parsing JSON line:', error); + } + + lineEnd = accumulator.indexOf('\n'); + } + }); + + // Handle the completion of the stream + stream.on('end', () => { + // Use the model response message if we don't have a full message yet + if (modelResponse?.message && !fullMessage) { + fullMessage = modelResponse.message; + } + + // Store the conversation ID and response ID + if (conversationId_) { + (this as any).lastConversationId = conversationId_; + } + + if (responseId) { + (this as any).lastResponseId = responseId; + } + + // Create the final response object + const parsedResponse: ParsedGrokResponse = { + fullMessage, + responseId, + title, + metadata, + modelResponse, + conversationId: conversationId_ + }; + + // Call the onComplete callback + if (callbacks.onComplete) { + callbacks.onComplete(parsedResponse); + } + }); + + // Handle errors + stream.on('error', (error: any) => { + console.error('Error streaming message from Mock Grok:', error); + + if (callbacks.onError) { + callbacks.onError(error); + } + }); + } catch (error: any) { + console.error('Error setting up streaming request to Mock Grok:', error); + + if (callbacks.onError) { + callbacks.onError(error); + } + } + } + + // Add a method to check if mock server is running + async checkMockServer(): Promise { + try { + const response = await gotScraping.get(`${this.mockBaseUrl}/health`); + return response.statusCode === 200; + } catch (error) { + console.error('Mock server is not running. Start it with: npm run mock-server'); + return false; + } + } +} \ No newline at end of file From 900d245f94a01d8071fbcea7ae4dfbfe7f8cbb85 Mon Sep 17 00:00:00 2001 From: John Pope Date: Sun, 6 Jul 2025 16:30:46 +1000 Subject: [PATCH 2/4] Add browser-compatible MockGrokAPI with fetch instead of got-scraping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 18 +++++ package.json | 1 + src/browserMockGrokApi.ts | 154 ++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 4 files changed, 174 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 src/browserMockGrokApi.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..daa0ce6 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run build:*)", + "Bash(mkdir:*)", + "Bash(cp:*)", + "Bash(npm install:*)", + "Bash(rm:*)", + "Bash(ls:*)", + "Bash(grep:*)", + "Bash(curl:*)", + "Bash(git -C /Users/johndpope/Documents/GitHub/atlas-ai-conversations-hub diff package.json)", + "Bash(git -C /Users/johndpope/Documents/GitHub/atlas-ai-conversations-hub diff vite.config.ts)", + "Bash(git -C /Users/johndpope/Documents/GitHub/atlas-ai-conversations-hub status)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 9045104..af90c93 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.1.2", "description": "TypeScript client for interacting with Grok AI with automated login and cookie management", "main": "dist/index.js", + "module": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/**/*", diff --git a/src/browserMockGrokApi.ts b/src/browserMockGrokApi.ts new file mode 100644 index 0000000..4e6ded2 --- /dev/null +++ b/src/browserMockGrokApi.ts @@ -0,0 +1,154 @@ +import { GrokAPI, GrokSendMessageOptions, ParsedGrokResponse, GrokStreamCallbacks } from './grokApi'; + +export class BrowserMockGrokAPI extends GrokAPI { + private mockBaseUrl: string; + private skipAuth: boolean; + + constructor(mockBaseUrl: string = 'http://localhost:3001', skipAuth: boolean = true) { + super([], ''); // Initialize with empty cookies + this.mockBaseUrl = mockBaseUrl; + this.skipAuth = skipAuth; + } + + // Override authentication methods for mock + isAuthenticated(): boolean { + return this.skipAuth || super.isAuthenticated(); + } + + async ensureAuthenticated(): Promise { + if (this.skipAuth) { + return true; + } + return super.ensureAuthenticated(); + } + + async login(username: string, password: string): Promise { + if (this.skipAuth) { + console.log('Mock mode: Skipping authentication'); + return true; + } + return super.login(username, password); + } + + async sendMessage(options: GrokSendMessageOptions): Promise { + if (!this.skipAuth && !this.isAuthenticated()) { + throw new Error('Not authenticated'); + } + + try { + const response = await fetch(`${this.mockBaseUrl}/grok/message`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: options.message, + customInstructions: options.customInstructions, + conversationId: options.conversationId, + disableSearch: options.disableSearch, + enableImageGeneration: options.enableImageGeneration, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return { + fullMessage: data.response, + responseId: data.responseId, + title: data.title, + metadata: data.metadata, + modelResponse: data.modelResponse, + conversationId: data.conversationId, + }; + } catch (error) { + console.error('Error sending message to mock Grok API:', error); + throw error; + } + } + + async sendMessageStream(options: GrokSendMessageOptions, callbacks: GrokStreamCallbacks): Promise { + if (!this.skipAuth && !this.isAuthenticated()) { + throw new Error('Not authenticated'); + } + + try { + const response = await fetch(`${this.mockBaseUrl}/grok/stream`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: options.message, + customInstructions: options.customInstructions, + conversationId: options.conversationId, + disableSearch: options.disableSearch, + enableImageGeneration: options.enableImageGeneration, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + let fullMessage = ''; + + if (!reader) { + throw new Error('No response body reader available'); + } + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n'); + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') { + callbacks.onComplete?.({ + fullMessage, + responseId: undefined, + title: undefined, + metadata: undefined, + modelResponse: undefined, + conversationId: undefined, + }); + return; + } + + try { + const parsed = JSON.parse(data); + if (parsed.token) { + fullMessage += parsed.token; + callbacks.onToken?.(parsed.token); + } + } catch (e) { + // Skip invalid JSON + } + } + } + } + } catch (error) { + console.error('Error in streaming message to mock Grok API:', error); + callbacks.onError?.(error); + throw error; + } + } + + async checkMockServer(): Promise { + try { + const response = await fetch(`${this.mockBaseUrl}/health`); + return response.ok; + } catch (error) { + console.error('Mock server check failed:', error); + return false; + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index b50a29c..4cab70b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './grokApi'; export * from './mockGrokApi'; +export * from './browserMockGrokApi'; export { GrokAPI as default } from './grokApi'; \ No newline at end of file From a947e42bf516269ada2cf3b0485930a81255bc5b Mon Sep 17 00:00:00 2001 From: John Pope Date: Mon, 7 Jul 2025 05:20:41 +1000 Subject: [PATCH 3/4] mock image upload-file - suggestions --- mock-server/server.ts | 561 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 487 insertions(+), 74 deletions(-) diff --git a/mock-server/server.ts b/mock-server/server.ts index 52c6543..a055e04 100644 --- a/mock-server/server.ts +++ b/mock-server/server.ts @@ -14,19 +14,111 @@ const conversations = new Map(); let conversationCounter = 1; let responseCounter = 1; -// Mock responses pool +// Mock responses pool with different types const mockResponses = [ - "I'm a mock Grok AI assistant. I can help you with various questions and tasks. What would you like to know?", - "That's an interesting question! As a mock assistant, I'm designed to simulate responses to help with development and testing.", - "I understand you're looking for information on that topic. While I'm just a mock endpoint, I can provide simulated responses.", - "Thanks for your question! This is a simulated response from the mock Grok backend.", - "I'm here to help! This mock server is designed to simulate the real Grok API for development purposes." + { + type: 'basic', + message: "I'm a mock Grok AI assistant. I can help you with various questions and tasks. What would you like to know?", + followUps: ["What are your capabilities?", "How can I use this API?", "Tell me about the features"], + hasWebSearch: false, + hasImages: false + }, + { + type: 'informative', + message: "That's an interesting question! As a mock assistant, I'm designed to simulate responses to help with development and testing. I can provide various types of responses including basic text, lists, and structured data.", + followUps: ["Can you show me different response types?", "What testing scenarios are supported?", "How realistic are the mock responses?"], + hasWebSearch: false, + hasImages: false + }, + { + type: 'helpful', + message: "I understand you're looking for information on that topic. While I'm just a mock endpoint, I can provide simulated responses with different formats and structures to help test your integration.", + followUps: ["Show me a list format", "Can you provide structured data?", "What other formats are available?"], + hasWebSearch: Math.random() > 0.7, + hasImages: false + }, + { + type: 'list', + message: "Here are some key features of this mock API:\n\n1. **Streaming responses** - Simulates real-time token streaming\n2. **Conversation management** - Maintains conversation state\n3. **Variable response types** - Different formats and structures\n4. **Follow-up suggestions** - Dynamic conversation prompts\n5. **Realistic timing** - Simulates natural response delays", + followUps: ["Tell me more about streaming", "How does conversation management work?", "What other features are planned?"], + hasWebSearch: false, + hasImages: false + }, + { + type: 'code', + message: "Here's a simple example of how to use this API:\n\n```javascript\nconst response = await fetch('/rest/app-chat/conversations/new', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: 'Hello!' })\n});\n```\n\nThis mock server supports both new conversations and continuing existing ones.", + followUps: ["Show me more code examples", "How do I handle streaming responses?", "What about error handling?"], + hasWebSearch: false, + hasImages: false + }, + { + type: 'thinking', + message: "šŸ¤” Let me think about that... This mock server can simulate different types of responses including thinking processes, code examples, lists, and structured data. It's designed to help developers test their integrations thoroughly.", + followUps: ["Can you simulate errors too?", "What about different response speeds?", "How do I test edge cases?"], + hasWebSearch: false, + hasImages: Math.random() > 0.8 + }, + { + type: 'creative', + message: "✨ Here's something creative! This mock server isn't just about boring test responses. It can simulate:\n\nšŸŽÆ **Targeted responses** based on your input\nšŸ”„ **Dynamic content** that changes each time\nšŸ“Š **Structured data** for complex testing\nšŸŽØ **Rich formatting** with emojis and markdown\n\nPerfect for comprehensive API testing!", + followUps: ["Show me more creative examples", "How do I test different scenarios?", "What about performance testing?"], + hasWebSearch: Math.random() > 0.6, + hasImages: Math.random() > 0.5 + } ]; +// Store for uploaded files +const uploadedFiles = new Map(); + // Generate mock streaming response -function generateStreamingResponse(message: string): string[] { +function generateStreamingResponse(message: string, hasFileAttachments = false): { chunks: string[], responseData: any, isImageGeneration?: boolean } { + // Check if this should be an image generation response + const isImageGeneration = hasFileAttachments && Math.random() > 0.3; // 70% chance for image generation if files are attached + + if (isImageGeneration) { + const imagePrompt = `moody, emotional illustration inspired by "${message}", depicting the uploaded image content with artistic enhancements, incorporating creative visual elements and atmospheric styling`; + const customResponse = `I generated images with the prompt: '${imagePrompt}'`; + + return { + chunks: [customResponse], + responseData: { + type: 'image_generation', + message: customResponse, + hasWebSearch: false, + hasImages: true, + followUps: [ + { + properties: { + messageType: "IMAGE_GEN", + followUpType: "STYLE" + }, + label: "artistic atmosphere", + toolOverrides: {} + }, + { + properties: { + messageType: "IMAGE_GEN", + followUpType: "ADD" + }, + label: "creative elements", + toolOverrides: {} + }, + { + properties: { + messageType: "IMAGE_GEN", + followUpType: "MODIFY" + }, + label: "mood enhancement", + toolOverrides: {} + } + ] + }, + isImageGeneration: true + }; + } + const baseResponse = mockResponses[Math.floor(Math.random() * mockResponses.length)]; - const customResponse = `You asked: "${message}". ${baseResponse}`; + const customResponse = `You asked: "${message}". ${baseResponse.message}`; // Split into chunks for streaming simulation const words = customResponse.split(' '); @@ -41,7 +133,127 @@ function generateStreamingResponse(message: string): string[] { } } - return chunks; + return { + chunks, + responseData: baseResponse + }; +} + +// Generate image generation streaming sequence +function generateImageGenerationSequence(message: string, responseId: string, userResponseId: string): any[] { + const imagePrompt = `moody, emotional illustration inspired by "${message}", depicting the uploaded image content with artistic enhancements, incorporating creative visual elements and atmospheric styling`; + const imageId = `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + + return [ + // Progress report for attachment preprocessing + { + result: { + response: { + progressReport: { + type: "ATTACHMENT_PREPROCESSING", + state: "COMPLETE", + progressPercentage: 100, + message: "Attachment preprocessing complete" + }, + responseId: responseId + } + } + }, + // Image dimensions + { + result: { + response: { + imageDimensions: { + width: 1024, + height: 1024 + }, + responseId: responseId + } + } + }, + // Image attachment info + { + result: { + response: { + imageAttachmentInfo: { + imageId: imageId, + prompt: imagePrompt, + style: "artistic", + aspectRatio: "1:1" + }, + responseId: responseId + } + } + }, + // Query action + { + result: { + response: { + queryAction: { + actionType: "IMAGE_GENERATION", + prompt: imagePrompt, + parameters: { + width: 1024, + height: 1024, + style: "artistic", + mood: "emotional" + } + }, + responseId: responseId + } + } + }, + // Streaming image generation responses with progress + { + result: { + response: { + streamingImageGenerationResponse: { + progress: 25, + status: "PROCESSING", + message: "Generating image..." + }, + responseId: responseId + } + } + }, + { + result: { + response: { + streamingImageGenerationResponse: { + progress: 50, + status: "PROCESSING", + message: "Refining details..." + }, + responseId: responseId + } + } + }, + { + result: { + response: { + streamingImageGenerationResponse: { + progress: 75, + status: "PROCESSING", + message: "Applying artistic effects..." + }, + responseId: responseId + } + } + }, + { + result: { + response: { + streamingImageGenerationResponse: { + progress: 100, + status: "COMPLETE", + message: "Image generation complete", + imageUrl: `https://example.com/generated-images/${imageId}.jpg` + }, + responseId: responseId + } + } + } + ]; } // Mock endpoint for new conversations @@ -63,7 +275,9 @@ app.post('/rest/app-chat/conversations/new', (req: Request, res: Response) => { ] }); - const chunks = generateStreamingResponse(message); + // Check if request has file attachments + const hasFileAttachments = req.body.fileAttachments && req.body.fileAttachments.length > 0; + const { chunks, responseData, isImageGeneration } = generateStreamingResponse(message, hasFileAttachments); // Send response as streaming JSON lines res.writeHead(200, { @@ -106,37 +320,36 @@ app.post('/rest/app-chat/conversations/new', (req: Request, res: Response) => { } }) + '\n'); - // Stream response tokens - let tokenIndex = 0; - const streamTokens = () => { - if (tokenIndex < chunks.length) { - const isLast = tokenIndex === chunks.length - 1; - - res.write(JSON.stringify({ - result: { - response: { - token: chunks[tokenIndex], - isThinking: false, - isSoftStop: false, - responseId: responseId + if (isImageGeneration) { + // Send image generation sequence + const imageSequence = generateImageGenerationSequence(message, responseId, userResponseId); + let sequenceIndex = 0; + + const streamImageSequence = () => { + if (sequenceIndex < imageSequence.length) { + res.write(JSON.stringify(imageSequence[sequenceIndex]) + '\n'); + sequenceIndex++; + setTimeout(streamImageSequence, 800); // Longer delays for image generation + } else { + // Send final response token + res.write(JSON.stringify({ + result: { + response: { + token: chunks[0], + isThinking: false, + isSoftStop: false, + responseId: responseId + } } - } - }) + '\n'); - - tokenIndex++; - - if (isLast) { - // Send final metadata + }) + '\n'); + + // Send final metadata with special follow-up format setTimeout(() => { res.write(JSON.stringify({ result: { response: { finalMetadata: { - followUpSuggestions: [ - "Can you tell me more about this topic?", - "What are the key points I should remember?", - "How does this relate to other concepts?" - ], + followUpSuggestions: responseData.followUps, feedbackLabels: ["helpful", "accurate", "clear"], toolsUsed: {} }, @@ -154,7 +367,9 @@ app.post('/rest/app-chat/conversations/new', (req: Request, res: Response) => { webSearchResults: [], xpostIds: [], xposts: [], - generatedImageUrls: [], + generatedImageUrls: [ + `https://example.com/generated-images/img_${Date.now()}.jpg` + ], imageAttachments: [], fileAttachments: [], cardAttachmentsJson: [], @@ -172,20 +387,107 @@ app.post('/rest/app-chat/conversations/new', (req: Request, res: Response) => { res.write(JSON.stringify({ result: { title: { - newTitle: `Mock: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}` + newTitle: `Image: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}` } } }) + '\n'); res.end(); }, 100); - } else { - setTimeout(streamTokens, 50 + Math.random() * 100); // Simulate realistic timing } - } - }; - - setTimeout(streamTokens, 200); // Initial delay + }; + + setTimeout(streamImageSequence, 500); + } else { + // Stream response tokens normally + let tokenIndex = 0; + const streamTokens = () => { + if (tokenIndex < chunks.length) { + const isLast = tokenIndex === chunks.length - 1; + + res.write(JSON.stringify({ + result: { + response: { + token: chunks[tokenIndex], + isThinking: false, + isSoftStop: false, + responseId: responseId + } + } + }) + '\n'); + + tokenIndex++; + + if (isLast) { + // Send final metadata + setTimeout(() => { + res.write(JSON.stringify({ + result: { + response: { + finalMetadata: { + followUpSuggestions: responseData.followUps || [ + "Can you tell me more about this topic?", + "What are the key points I should remember?", + "How does this relate to other concepts?" + ], + feedbackLabels: ["helpful", "accurate", "clear"], + toolsUsed: {} + }, + modelResponse: { + responseId: responseId, + message: chunks.join(''), + sender: "assistant", + createTime: new Date().toISOString(), + parentResponseId: userResponseId, + manual: false, + partial: false, + shared: false, + query: message, + queryType: "text", + webSearchResults: responseData.hasWebSearch ? [ + { + title: "Mock Search Result", + url: "https://example.com/mock-result", + snippet: "This is a mock web search result for testing purposes." + } + ] : [], + xpostIds: [], + xposts: [], + generatedImageUrls: responseData.hasImages ? [ + "https://example.com/mock-generated-image.jpg" + ] : [], + imageAttachments: [], + fileAttachments: [], + cardAttachmentsJson: [], + fileUris: [], + fileAttachmentsMetadata: [], + isControl: false, + steps: [], + mediaTypes: [] + } + } + } + }) + '\n'); + + // Send title update + res.write(JSON.stringify({ + result: { + title: { + newTitle: `Mock: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}` + } + } + }) + '\n'); + + res.end(); + }, 100); + } else { + setTimeout(streamTokens, 50 + Math.random() * 100); // Simulate realistic timing + } + } + }; + + setTimeout(streamTokens, 200); // Initial delay + } }); // Mock endpoint for continuing conversations @@ -198,7 +500,9 @@ app.post('/rest/app-chat/conversations/:conversationId/responses', (req: Request const responseId = `resp_${responseCounter++}`; const userResponseId = `user_${responseCounter++}`; - const chunks = generateStreamingResponse(message); + // Check if request has file attachments + const hasFileAttachments = req.body.fileAttachments && req.body.fileAttachments.length > 0; + const { chunks, responseData, isImageGeneration } = generateStreamingResponse(message, hasFileAttachments); // Send response as streaming JSON lines res.writeHead(200, { @@ -225,37 +529,36 @@ app.post('/rest/app-chat/conversations/:conversationId/responses', (req: Request } }) + '\n'); - // Stream response tokens - let tokenIndex = 0; - const streamTokens = () => { - if (tokenIndex < chunks.length) { - const isLast = tokenIndex === chunks.length - 1; - - res.write(JSON.stringify({ - result: { - response: { - token: chunks[tokenIndex], - isThinking: false, - isSoftStop: false, - responseId: responseId + if (isImageGeneration) { + // Send image generation sequence + const imageSequence = generateImageGenerationSequence(message, responseId, userResponseId); + let sequenceIndex = 0; + + const streamImageSequence = () => { + if (sequenceIndex < imageSequence.length) { + res.write(JSON.stringify(imageSequence[sequenceIndex]) + '\n'); + sequenceIndex++; + setTimeout(streamImageSequence, 800); // Longer delays for image generation + } else { + // Send final response token + res.write(JSON.stringify({ + result: { + response: { + token: chunks[0], + isThinking: false, + isSoftStop: false, + responseId: responseId + } } - } - }) + '\n'); - - tokenIndex++; - - if (isLast) { - // Send final metadata + }) + '\n'); + + // Send final metadata with special follow-up format setTimeout(() => { res.write(JSON.stringify({ result: { response: { finalMetadata: { - followUpSuggestions: [ - "What else would you like to know?", - "Can you elaborate on that?", - "Are there any related topics?" - ], + followUpSuggestions: responseData.followUps, feedbackLabels: ["helpful", "accurate", "clear"], toolsUsed: {} }, @@ -273,7 +576,9 @@ app.post('/rest/app-chat/conversations/:conversationId/responses', (req: Request webSearchResults: [], xpostIds: [], xposts: [], - generatedImageUrls: [], + generatedImageUrls: [ + `https://example.com/generated-images/img_${Date.now()}.jpg` + ], imageAttachments: [], fileAttachments: [], cardAttachmentsJson: [], @@ -289,13 +594,120 @@ app.post('/rest/app-chat/conversations/:conversationId/responses', (req: Request res.end(); }, 100); - } else { - setTimeout(streamTokens, 50 + Math.random() * 100); } - } + }; + + setTimeout(streamImageSequence, 500); + } else { + // Stream response tokens normally + let tokenIndex = 0; + const streamTokens = () => { + if (tokenIndex < chunks.length) { + const isLast = tokenIndex === chunks.length - 1; + + res.write(JSON.stringify({ + result: { + response: { + token: chunks[tokenIndex], + isThinking: false, + isSoftStop: false, + responseId: responseId + } + } + }) + '\n'); + + tokenIndex++; + + if (isLast) { + // Send final metadata + setTimeout(() => { + res.write(JSON.stringify({ + result: { + response: { + finalMetadata: { + followUpSuggestions: responseData.followUps || [ + "What else would you like to know?", + "Can you elaborate on that?", + "Are there any related topics?" + ], + feedbackLabels: ["helpful", "accurate", "clear"], + toolsUsed: {} + }, + modelResponse: { + responseId: responseId, + message: chunks.join(''), + sender: "assistant", + createTime: new Date().toISOString(), + parentResponseId: userResponseId, + manual: false, + partial: false, + shared: false, + query: message, + queryType: "text", + webSearchResults: responseData.hasWebSearch ? [ + { + title: "Mock Search Result", + url: "https://example.com/mock-result", + snippet: "This is a mock web search result for testing purposes." + } + ] : [], + xpostIds: [], + xposts: [], + generatedImageUrls: responseData.hasImages ? [ + "https://example.com/mock-generated-image.jpg" + ] : [], + imageAttachments: [], + fileAttachments: [], + cardAttachmentsJson: [], + fileUris: [], + fileAttachmentsMetadata: [], + isControl: false, + steps: [], + mediaTypes: [] + } + } + } + }) + '\n'); + + res.end(); + }, 100); + } else { + setTimeout(streamTokens, 50 + Math.random() * 100); + } + } + }; + + setTimeout(streamTokens, 200); + } +}); + +// Mock file upload endpoint +app.post('/rest/app-chat/upload-file', (req: Request, res: Response) => { + const { fileName, fileMimeType, content } = req.body; + + console.log(`Mock: File upload - ${fileName} (${fileMimeType})`); + + // Generate a mock file metadata response + const fileMetadataId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const userId = 'mock-user-id'; + + const fileMetadata = { + fileMetadataId, + fileMimeType, + fileName, + fileUri: `users/${userId}/${fileMetadataId}/content`, + parsedFileUri: "", + createTime: new Date().toISOString(), + fileSource: "SELF_UPLOAD_FILE_SOURCE" }; - setTimeout(streamTokens, 200); + // Store the file data + uploadedFiles.set(fileMetadataId, { + ...fileMetadata, + content + }); + + res.json(fileMetadata); }); // Health check endpoint @@ -310,6 +722,7 @@ app.listen(PORT, () => { console.log('Endpoints:'); console.log(` POST http://localhost:${PORT}/rest/app-chat/conversations/new`); console.log(` POST http://localhost:${PORT}/rest/app-chat/conversations/:id/responses`); + console.log(` POST http://localhost:${PORT}/rest/app-chat/upload-file`); }); export default app; \ No newline at end of file From 8205ace3f6ba4c2fa894984738acf691e3451f66 Mon Sep 17 00:00:00 2001 From: John Pope Date: Mon, 7 Jul 2025 05:24:12 +1000 Subject: [PATCH 4/4] update readme --- README.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 91a730d..e8b23d6 100644 --- a/README.md +++ b/README.md @@ -266,10 +266,200 @@ The `ParsedGrokResponse` includes: - `modelResponse`: Detailed model response object - `conversationId`: ID of the conversation +## Mock Server for Development + +This library includes a comprehensive mock server that simulates the Grok API endpoints without requiring authentication. This is perfect for development, testing, and integration work. + +### Quick Start with Mock Server + +1. **Start the mock server:** + ```bash + npm run mock-server + # or + pnpm run mock-server + ``` + +2. **Use the MockGrokAPI class:** + ```typescript + import { MockGrokAPI } from 'grok-api-ts'; + + const mockGrokApi = new MockGrokAPI('http://localhost:3001', true); + + // Check if mock server is running + const isRunning = await mockGrokApi.checkMockServer(); + + // Send message (no authentication needed) + const response = await mockGrokApi.sendMessage({ + message: "Hello, mock Grok!" + }); + + console.log(response.fullMessage); + ``` + +3. **Run the included mock example:** + ```bash + npm run mock-example + # or + pnpm run mock-example + ``` + +### Mock Server Features + +- **No Authentication Required**: Skip complex browser-based login +- **Realistic Streaming**: Token-by-token response streaming simulation +- **Conversation Management**: Maintains conversation state +- **Image Generation Support**: Simulates image generation with progress tracking +- **File Upload Support**: Mock file attachment handling +- **Follow-up Suggestions**: Dynamic conversation prompts +- **Error Handling**: Proper error responses and status codes +- **CORS Support**: Configured for cross-origin requests + +### Mock Server Endpoints + +- `POST /rest/app-chat/conversations/new` - Create new conversation +- `POST /rest/app-chat/conversations/:id/responses` - Continue conversation +- `POST /rest/app-chat/upload-file` - Upload file attachments +- `GET /health` - Health check endpoint + +### Switching Between Real and Mock APIs + +```typescript +// For development/testing +const mockGrokApi = new MockGrokAPI('http://localhost:3001', true); + +// For production +const realGrokApi = new GrokAPI(); +await realGrokApi.login('username', 'password'); + +// Use the same interface for both +const response = await grokApi.sendMessage({ + message: "Hello Grok!" +}); +``` + +## Grok API Response Formats + +The library handles multiple response formats from the Grok API: + +### Standard Text Response + +```typescript +{ + fullMessage: string; // Complete response text + responseId: string; // Unique response identifier + title?: string; // Conversation title + conversationId?: string; // Conversation identifier + metadata?: { + followUpSuggestions: string[]; // Suggested follow-up questions + feedbackLabels: string[]; // Available feedback options + toolsUsed: object; // Tools used in response + }; + modelResponse?: { + message: string; // Response text + webSearchResults?: Array<{ // Web search results (if enabled) + title: string; + url: string; + snippet: string; + }>; + generatedImageUrls?: string[]; // Generated image URLs + imageAttachments?: any[]; // Image attachments + fileAttachments?: any[]; // File attachments + // ... additional metadata + }; +} +``` + +### Streaming Response Format + +When using streaming, the response is built incrementally: + +```typescript +// Token-by-token streaming +onToken: (token: string) => void; + +// Complete response when finished +onComplete: (response: ParsedGrokResponse) => void; + +// Error handling +onError: (error: any) => void; +``` + +### Image Generation Response + +When image generation is enabled: + +```typescript +{ + fullMessage: "I generated images with the prompt: '...'"; + modelResponse: { + generatedImageUrls: [ + "https://example.com/generated-image.jpg" + ]; + // ... other properties + }; + metadata: { + followUpSuggestions: [ + { + properties: { messageType: "IMAGE_GEN", followUpType: "STYLE" }, + label: "artistic atmosphere" + } + // ... more suggestions + ]; + }; +} +``` + +### Error Response Format + +```typescript +{ + error: string; // Error message + statusCode?: number; // HTTP status code + details?: any; // Additional error details +} +``` + +## Configuration Options + +### GrokSendMessageOptions + +```typescript +interface GrokSendMessageOptions { + message: string; // Required: The message to send + conversationId?: string; // Conversation ID ('new' for new conversation) + parentResponseId?: string; // Parent response ID for threading + disableSearch?: boolean; // Disable web search (default: false) + enableImageGeneration?: boolean; // Enable image generation (default: true) + customInstructions?: string; // Custom instructions for the response + forceConcise?: boolean; // Request concise response (default: false) + imageAttachments?: any[]; // Image attachments + isReasoning?: boolean; // Enable reasoning mode (default: false) +} +``` + +### MockGrokAPI Constructor + +```typescript +constructor( + mockBaseUrl: string = 'http://localhost:3001', // Mock server URL + skipAuth: boolean = true // Skip authentication +) +``` + +## Development Benefits + +1. **Faster Development**: No authentication delays +2. **Offline Testing**: Work without internet connection +3. **Predictable Responses**: Consistent mock responses for testing +4. **Rate Limit Free**: No API rate limits during development +5. **Easy Debugging**: Full control over response timing and content +6. **Image Generation Testing**: Simulate image generation workflows +7. **File Upload Testing**: Test file attachment handling + ## Requirements - Node.js 14+ -- Chrome browser installed (used by patchright for authentication) +- Chrome browser installed (used by patchright for authentication) - *Not required for mock server* ## License