├── .gitignore ├── postcss.config.js ├── frontend ├── src │ ├── index.css │ ├── main.jsx │ ├── App.css │ └── App.jsx └── index.html ├── vite.config.js ├── tailwind.config.js ├── bin └── claude-log ├── package.json ├── README.md ├── jsonl_logger.js ├── claude_logger.js ├── direct_capture.js └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | /logs 4 | package-lock.json 5 | 6 | **/.claude/settings.local.json 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | body { 7 | @apply bg-gray-50 font-sans antialiased; 8 | } 9 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | root: './frontend', 7 | build: { 8 | outDir: '../dist' 9 | } 10 | }) -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./frontend/index.html", 5 | "./frontend/src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) -------------------------------------------------------------------------------- /bin/claude-log: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Claude API Logger - Simple wrapper script 5 | */ 6 | 7 | const path = require("path"); 8 | 9 | // Locate the claude-logger main script 10 | const mainScriptPath = path.resolve(__dirname, "../claude_logger.js"); 11 | 12 | require(mainScriptPath); 13 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Claude Code Logger Viewer 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claude-logger", 3 | "version": "1.1.0", 4 | "description": "Log Anthropic API calls made through the Claude CLI", 5 | "main": "claude_logger.js", 6 | "bin": { 7 | "claude-log": "./bin/claude-log" 8 | }, 9 | "scripts": { 10 | "start": "node claude_logger.js", 11 | "dev": "vite", 12 | "build": "vite build", 13 | "preview": "vite preview" 14 | }, 15 | "keywords": [ 16 | "claude", 17 | "api", 18 | "logger", 19 | "anthropic" 20 | ], 21 | "author": "", 22 | "license": "MIT", 23 | "dependencies": { 24 | "react": "^18.3.1", 25 | "react-dom": "^18.3.1" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.3.3", 29 | "@types/react-dom": "^18.3.0", 30 | "@vitejs/plugin-react": "^4.3.1", 31 | "vite": "^5.3.1", 32 | "tailwindcss": "^3.4.0", 33 | "autoprefixer": "^10.4.16", 34 | "postcss": "^8.4.32" 35 | }, 36 | "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Claude Code API Logger 2 | 3 | A simple tool to log API calls made by the Claude Code CLI to Anthropic's API. 4 | 5 | ## Disclaimer 6 | 7 | **FOR RESEARCH PURPOSES ONLY** 8 | 9 | This tool is provided strictly for research and educational purposes only. By using this software, you agree to the following: 10 | 11 | 1. The author makes no warranties or representations of any kind concerning this software and accepts no liability for its use. 12 | 2. The author assumes no responsibility whatsoever for any consequences arising from the use of this tool. 13 | 3. Users are solely responsible for ensuring their use of this tool complies with all applicable laws, regulations, and Anthropic's terms of service. 14 | 4. This tool is not officially affiliated with, authorized, maintained, sponsored, or endorsed by Anthropic or any of its affiliates. 15 | 16 | Use at your own risk. 17 | 18 | This project itself was developed with [Claude Code](https://claude.ai/code). 19 | 20 | ## Installation 21 | 22 | ### Global Installation (recommended) 23 | 24 | ```bash 25 | npm install -g . 26 | ``` 27 | 28 | This will make the `claude-log` command available globally. 29 | 30 | ### Local Installation 31 | 32 | ```bash 33 | npm install . 34 | ``` 35 | 36 | ## Usage 37 | 38 | ```bash 39 | claude-log [options] [claude options] 40 | ``` 41 | 42 | All arguments after the options are passed directly to the Claude CLI. 43 | 44 | ### Options 45 | 46 | - `--help`: Show help message 47 | - `--log_dir=DIR`: Specify directory for logs (default: ./logs) 48 | - `--print`: Show debug messages (default: off) 49 | - `--install-alias`: Install 'claude' alias in your shell configuration 50 | 51 | ### Examples 52 | 53 | ```bash 54 | # Basic usage - run Claude with API logging 55 | claude-log 56 | 57 | # Run Claude with a prompt 58 | claude-log -p "What is Rust and why do people care?" 59 | 60 | # Enable debug mode to show more verbose output 61 | claude-log --print 62 | ``` 63 | 64 | ## Setting up an alias 65 | 66 | For even easier usage, you can set up a shell alias: 67 | 68 | ### Bash/ZSH 69 | 70 | Add to your `.bashrc` or `.zshrc`: 71 | 72 | ```bash 73 | alias claude='claude-log' 74 | ``` 75 | 76 | ### Fish shell 77 | 78 | ```fish 79 | alias claude='claude-log' 80 | funcsave claude 81 | ``` 82 | 83 | This lets you use `claude` as normal, but with all API calls logged. 84 | 85 | ## Log Files 86 | 87 | Log files are stored in a `logs` directory within your current project. These logs follow the format `project-name_timestamp.json`. 88 | 89 | The log files are formatted as a JSON array of request/response objects with metadata about the session. 90 | 91 | ## Visualize log 92 | ``` 93 | pnpm i 94 | pnpm dev 95 | ``` -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .log-viewer { 2 | max-width: 1200px; 3 | margin: 0 auto; 4 | padding: 20px; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 6 | } 7 | 8 | .log-viewer h1 { 9 | text-align: center; 10 | color: #333; 11 | margin-bottom: 30px; 12 | font-size: 2rem; 13 | } 14 | 15 | /* Drop Zone */ 16 | .drop-zone { 17 | border: 3px dashed #ccc; 18 | border-radius: 10px; 19 | padding: 60px 20px; 20 | text-align: center; 21 | background-color: #fafafa; 22 | transition: all 0.3s ease; 23 | cursor: pointer; 24 | } 25 | 26 | .drop-zone:hover, 27 | .drop-zone.drag-over { 28 | border-color: #007acc; 29 | background-color: #f0f8ff; 30 | } 31 | 32 | .drop-zone-content p { 33 | font-size: 1.2rem; 34 | color: #666; 35 | margin: 0 0 10px 0; 36 | } 37 | 38 | .drop-zone-content small { 39 | color: #999; 40 | } 41 | 42 | /* Summary */ 43 | .summary { 44 | background: white; 45 | padding: 20px; 46 | border-radius: 8px; 47 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 48 | margin-bottom: 20px; 49 | } 50 | 51 | .summary h2 { 52 | margin-top: 0; 53 | color: #333; 54 | } 55 | 56 | .summary p { 57 | margin: 8px 0; 58 | font-size: 1rem; 59 | } 60 | 61 | /* Request List */ 62 | .requests-list { 63 | space: 15px; 64 | } 65 | 66 | .request-item { 67 | background: white; 68 | border-radius: 8px; 69 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 70 | margin-bottom: 15px; 71 | overflow: hidden; 72 | transition: box-shadow 0.2s ease; 73 | } 74 | 75 | .request-item:hover { 76 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); 77 | } 78 | 79 | /* Request Header */ 80 | .request-header { 81 | padding: 15px 20px; 82 | cursor: pointer; 83 | display: flex; 84 | justify-content: space-between; 85 | align-items: center; 86 | background: #f8f9fa; 87 | border-bottom: 1px solid #e9ecef; 88 | transition: background-color 0.2s ease; 89 | } 90 | 91 | .request-header:hover { 92 | background: #e9ecef; 93 | } 94 | 95 | .request-summary { 96 | display: flex; 97 | gap: 20px; 98 | align-items: center; 99 | flex-wrap: wrap; 100 | } 101 | 102 | .model { 103 | font-weight: 600; 104 | color: #007acc; 105 | font-size: 1rem; 106 | } 107 | 108 | .tokens { 109 | color: #666; 110 | font-size: 0.9rem; 111 | } 112 | 113 | .cached { 114 | color: #28a745; 115 | font-weight: 500; 116 | } 117 | 118 | .time { 119 | color: #666; 120 | font-size: 0.85rem; 121 | } 122 | 123 | .duration { 124 | color: #666; 125 | font-size: 0.85rem; 126 | background: #e9ecef; 127 | padding: 2px 8px; 128 | border-radius: 4px; 129 | } 130 | 131 | .expand-icon { 132 | font-size: 1.2rem; 133 | color: #666; 134 | transition: transform 0.2s ease; 135 | } 136 | 137 | /* Request Details */ 138 | .request-details { 139 | padding: 20px; 140 | background: white; 141 | } 142 | 143 | .details-grid { 144 | display: grid; 145 | grid-template-columns: 1fr 1fr; 146 | gap: 30px; 147 | margin-bottom: 25px; 148 | } 149 | 150 | @media (max-width: 768px) { 151 | .details-grid { 152 | grid-template-columns: 1fr; 153 | gap: 20px; 154 | } 155 | } 156 | 157 | .detail-section { 158 | background: #f8f9fa; 159 | padding: 15px; 160 | border-radius: 6px; 161 | } 162 | 163 | .detail-section h4 { 164 | margin: 0 0 15px 0; 165 | color: #333; 166 | font-size: 1.1rem; 167 | border-bottom: 2px solid #007acc; 168 | padding-bottom: 5px; 169 | } 170 | 171 | .detail-section p { 172 | margin: 8px 0; 173 | font-size: 0.9rem; 174 | line-height: 1.4; 175 | } 176 | 177 | .detail-section strong { 178 | color: #333; 179 | font-weight: 600; 180 | } 181 | 182 | /* Messages Section */ 183 | .messages-section { 184 | margin-top: 25px; 185 | } 186 | 187 | .message-block { 188 | margin-bottom: 20px; 189 | } 190 | 191 | .message-block h4 { 192 | margin: 0 0 10px 0; 193 | color: #333; 194 | font-size: 1rem; 195 | border-left: 4px solid #007acc; 196 | padding-left: 12px; 197 | } 198 | 199 | .message-content { 200 | background: #f8f9fa; 201 | border: 1px solid #e9ecef; 202 | border-radius: 6px; 203 | padding: 15px; 204 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; 205 | font-size: 0.85rem; 206 | line-height: 1.5; 207 | white-space: pre-wrap; 208 | word-wrap: break-word; 209 | max-height: 200px; 210 | overflow-y: auto; 211 | margin: 0; 212 | } 213 | 214 | /* Raw Data */ 215 | .raw-data { 216 | margin-top: 25px; 217 | border: 1px solid #e9ecef; 218 | border-radius: 6px; 219 | } 220 | 221 | .raw-data summary { 222 | padding: 15px; 223 | background: #f8f9fa; 224 | cursor: pointer; 225 | font-weight: 600; 226 | color: #333; 227 | border-radius: 6px 6px 0 0; 228 | } 229 | 230 | .raw-data summary:hover { 231 | background: #e9ecef; 232 | } 233 | 234 | .raw-section { 235 | padding: 15px; 236 | border-top: 1px solid #e9ecef; 237 | } 238 | 239 | .raw-section h5 { 240 | margin: 0 0 10px 0; 241 | color: #666; 242 | font-size: 0.9rem; 243 | text-transform: uppercase; 244 | letter-spacing: 0.5px; 245 | } 246 | 247 | .raw-section pre { 248 | background: #f8f9fa; 249 | border: 1px solid #e9ecef; 250 | border-radius: 4px; 251 | padding: 15px; 252 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; 253 | font-size: 0.8rem; 254 | line-height: 1.4; 255 | white-space: pre-wrap; 256 | word-wrap: break-word; 257 | max-height: 300px; 258 | overflow-y: auto; 259 | margin: 0; 260 | } 261 | 262 | /* Responsive Design */ 263 | @media (max-width: 768px) { 264 | .log-viewer { 265 | padding: 15px; 266 | } 267 | 268 | .log-viewer h1 { 269 | font-size: 1.6rem; 270 | } 271 | 272 | .request-summary { 273 | flex-direction: column; 274 | align-items: flex-start; 275 | gap: 8px; 276 | } 277 | 278 | .request-header { 279 | flex-direction: column; 280 | align-items: stretch; 281 | } 282 | 283 | .expand-icon { 284 | align-self: flex-end; 285 | } 286 | 287 | .drop-zone { 288 | padding: 40px 15px; 289 | } 290 | 291 | .summary, 292 | .request-details { 293 | padding: 15px; 294 | } 295 | } 296 | 297 | /* Scrollbar styling */ 298 | ::-webkit-scrollbar { 299 | width: 8px; 300 | } 301 | 302 | ::-webkit-scrollbar-track { 303 | background: #f1f1f1; 304 | border-radius: 4px; 305 | } 306 | 307 | ::-webkit-scrollbar-thumb { 308 | background: #c1c1c1; 309 | border-radius: 4px; 310 | } 311 | 312 | ::-webkit-scrollbar-thumb:hover { 313 | background: #a8a8a8; 314 | } -------------------------------------------------------------------------------- /jsonl_logger.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { promisify } = require('util'); 4 | 5 | const appendFile = promisify(fs.appendFile); 6 | const writeFile = promisify(fs.writeFile); 7 | const readFile = promisify(fs.readFile); 8 | const rename = promisify(fs.rename); 9 | const stat = promisify(fs.stat); 10 | 11 | class JsonLinesLogger { 12 | constructor(filePath, options = {}) { 13 | this.filePath = filePath; 14 | this.queue = []; 15 | this.isWriting = false; 16 | this.isClosed = false; 17 | 18 | // Configuration options 19 | this.maxQueueSize = options.maxQueueSize || 100; 20 | this.flushInterval = options.flushInterval || 2000; 21 | this.maxRetries = options.maxRetries || 3; 22 | this.retryDelay = options.retryDelay || 1000; 23 | 24 | // Start periodic flush 25 | this.flushIntervalId = setInterval(() => { 26 | this.flush().catch(err => { 27 | console.error('Error during periodic flush:', err); 28 | }); 29 | }, this.flushInterval); 30 | 31 | // Ensure logs are written on process exit 32 | this.setupExitHandlers(); 33 | } 34 | 35 | setupExitHandlers() { 36 | const exitHandler = () => { 37 | this.close(); 38 | }; 39 | 40 | process.on('exit', exitHandler); 41 | process.on('SIGINT', exitHandler); 42 | process.on('SIGTERM', exitHandler); 43 | process.on('uncaughtException', (err) => { 44 | console.error('Uncaught exception:', err); 45 | exitHandler(); 46 | process.exit(1); 47 | }); 48 | } 49 | 50 | /** 51 | * Add a log entry to the queue 52 | * @param {Object} logEntry - The log entry to add 53 | * @returns {Promise} - Resolves when the entry is queued 54 | */ 55 | async log(logEntry) { 56 | if (this.isClosed) { 57 | throw new Error('Logger is closed'); 58 | } 59 | 60 | // Add timestamp if not present 61 | if (!logEntry.timestamp) { 62 | logEntry.timestamp = new Date().toISOString(); 63 | } 64 | 65 | this.queue.push(logEntry); 66 | 67 | // Flush if queue is getting large 68 | if (this.queue.length >= this.maxQueueSize) { 69 | await this.flush(); 70 | } 71 | } 72 | 73 | /** 74 | * Flush the queue to disk 75 | * @returns {Promise} 76 | */ 77 | async flush() { 78 | if (this.isWriting || this.queue.length === 0) { 79 | return; 80 | } 81 | 82 | this.isWriting = true; 83 | const logsToWrite = [...this.queue]; 84 | this.queue = []; 85 | 86 | try { 87 | await this.writeLogsWithRetry(logsToWrite); 88 | } catch (error) { 89 | // Put logs back in queue if write failed 90 | this.queue.unshift(...logsToWrite); 91 | throw error; 92 | } finally { 93 | this.isWriting = false; 94 | } 95 | } 96 | 97 | /** 98 | * Write logs with retry logic 99 | * @param {Array} logs - Array of log entries 100 | * @returns {Promise} 101 | */ 102 | async writeLogsWithRetry(logs) { 103 | let lastError; 104 | 105 | for (let attempt = 0; attempt < this.maxRetries; attempt++) { 106 | try { 107 | await this.writeLogs(logs); 108 | return; 109 | } catch (error) { 110 | lastError = error; 111 | if (attempt < this.maxRetries - 1) { 112 | await new Promise(resolve => setTimeout(resolve, this.retryDelay)); 113 | } 114 | } 115 | } 116 | 117 | throw lastError; 118 | } 119 | 120 | /** 121 | * Write logs to file 122 | * @param {Array} logs - Array of log entries 123 | * @returns {Promise} 124 | */ 125 | async writeLogs(logs) { 126 | // Ensure directory exists 127 | const dir = path.dirname(this.filePath); 128 | await fs.promises.mkdir(dir, { recursive: true }); 129 | 130 | // Convert logs to JSON Lines format 131 | const jsonLines = logs.map(log => JSON.stringify(log)).join('\n') + '\n'; 132 | 133 | // Check if file exists 134 | let fileExists = false; 135 | try { 136 | await stat(this.filePath); 137 | fileExists = true; 138 | } catch (error) { 139 | if (error.code !== 'ENOENT') { 140 | throw error; 141 | } 142 | } 143 | 144 | if (fileExists) { 145 | // Append to existing file 146 | await appendFile(this.filePath, jsonLines, 'utf8'); 147 | } else { 148 | // Create new file 149 | await writeFile(this.filePath, jsonLines, 'utf8'); 150 | } 151 | } 152 | 153 | /** 154 | * Close the logger and flush remaining logs 155 | */ 156 | close() { 157 | if (this.isClosed) { 158 | return; 159 | } 160 | 161 | this.isClosed = true; 162 | 163 | if (this.flushIntervalId) { 164 | clearInterval(this.flushIntervalId); 165 | this.flushIntervalId = null; 166 | } 167 | 168 | // Synchronous flush on close 169 | if (this.queue.length > 0) { 170 | const logsToWrite = [...this.queue]; 171 | const jsonLines = logsToWrite.map(log => JSON.stringify(log)).join('\n') + '\n'; 172 | 173 | try { 174 | // Ensure directory exists synchronously 175 | const dir = path.dirname(this.filePath); 176 | if (!fs.existsSync(dir)) { 177 | fs.mkdirSync(dir, { recursive: true }); 178 | } 179 | 180 | // Append synchronously 181 | fs.appendFileSync(this.filePath, jsonLines, 'utf8'); 182 | this.queue = []; 183 | } catch (error) { 184 | console.error('Error during synchronous flush:', error); 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Migrate existing JSON log file to JSON Lines format 191 | * @param {string} jsonFilePath - Path to existing JSON log file 192 | * @param {string} jsonlFilePath - Path to new JSON Lines file 193 | * @returns {Promise} 194 | */ 195 | static async migrateFromJson(jsonFilePath, jsonlFilePath) { 196 | try { 197 | // Check if JSON file exists 198 | await stat(jsonFilePath); 199 | } catch (error) { 200 | if (error.code === 'ENOENT') { 201 | // No file to migrate 202 | return; 203 | } 204 | throw error; 205 | } 206 | 207 | // Read existing JSON file 208 | const jsonContent = await readFile(jsonFilePath, 'utf8'); 209 | let logs; 210 | 211 | try { 212 | logs = JSON.parse(jsonContent); 213 | } catch (error) { 214 | console.error('Error parsing existing log file:', error); 215 | // If parse fails, backup the corrupt file 216 | const backupPath = jsonFilePath + '.backup.' + Date.now(); 217 | await rename(jsonFilePath, backupPath); 218 | console.log(`Corrupt log file backed up to: ${backupPath}`); 219 | return; 220 | } 221 | 222 | if (!Array.isArray(logs)) { 223 | console.error('Existing log file is not an array'); 224 | return; 225 | } 226 | 227 | // Convert to JSON Lines 228 | const jsonLines = logs.map(log => JSON.stringify(log)).join('\n') + '\n'; 229 | 230 | // Write to new file 231 | const dir = path.dirname(jsonlFilePath); 232 | await fs.promises.mkdir(dir, { recursive: true }); 233 | await writeFile(jsonlFilePath, jsonLines, 'utf8'); 234 | 235 | // Backup original file 236 | const backupPath = jsonFilePath + '.migrated.' + Date.now(); 237 | await rename(jsonFilePath, backupPath); 238 | 239 | console.log(`Migrated ${logs.length} log entries from JSON to JSON Lines format`); 240 | console.log(`Original file backed up to: ${backupPath}`); 241 | } 242 | 243 | /** 244 | * Read logs from JSON Lines file 245 | * @param {string} filePath - Path to JSON Lines file 246 | * @returns {Promise} - Array of log entries 247 | */ 248 | static async readJsonLines(filePath) { 249 | try { 250 | const content = await readFile(filePath, 'utf8'); 251 | const lines = content.trim().split('\n'); 252 | const logs = []; 253 | 254 | for (const line of lines) { 255 | if (line.trim()) { 256 | try { 257 | logs.push(JSON.parse(line)); 258 | } catch (error) { 259 | console.error('Error parsing JSON line:', error); 260 | } 261 | } 262 | } 263 | 264 | return logs; 265 | } catch (error) { 266 | if (error.code === 'ENOENT') { 267 | return []; 268 | } 269 | throw error; 270 | } 271 | } 272 | } 273 | 274 | module.exports = JsonLinesLogger; -------------------------------------------------------------------------------- /claude_logger.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Claude API Logger - Captures Claude API requests and responses 5 | * Usage: claude-log [options] [claude options] 6 | * 7 | * Options: 8 | * --log_dir=DIR Specify directory for logs (default: ./logs) 9 | * --print Show debug information 10 | * --install-alias Install 'claude' alias in your shell configuration 11 | */ 12 | 13 | const fs = require("fs"); 14 | const path = require("path"); 15 | const { spawn } = require("child_process"); 16 | const crypto = require("crypto"); 17 | const os = require("os"); 18 | const { execSync } = require("child_process"); 19 | const JsonLinesLogger = require("./jsonl_logger"); 20 | 21 | // Get version from package.json 22 | const packageJson = require("./package.json"); 23 | const version = packageJson.version; 24 | 25 | // Help text function 26 | function showHelp() { 27 | console.log( 28 | ` 29 | Claude Logger - Captures Claude API requests and responses 30 | Usage: claude-log [options] [claude options] 31 | 32 | Options: 33 | --help Show this help message and then continue with the command 34 | --log_dir=DIR Specify directory for logs (default: ./logs) 35 | --print Show debug information 36 | --install-alias Install 'claude' alias in your shell configuration 37 | 38 | The claude-log command passes all other arguments to the claude CLI. 39 | ` 40 | ); 41 | } 42 | 43 | // Parse command line arguments 44 | const args = process.argv.slice(2); 45 | let logDir = null; 46 | let projectLogEnabled = true; 47 | let printDebug = false; 48 | let showHelpMessage = false; 49 | let installAlias = false; 50 | let claudeArgs = []; 51 | 52 | // Process arguments 53 | for (let i = 0; i < args.length; i++) { 54 | const arg = args[i]; 55 | if (arg === "--help") { 56 | showHelpMessage = true; 57 | claudeArgs.push(arg); // Pass --help to Claude 58 | } else if (arg.startsWith("--log_dir=")) { 59 | logDir = arg.split("=")[1]; 60 | } else if (arg === "--log_dir" && i < args.length - 1) { 61 | logDir = args[i + 1]; 62 | i++; 63 | } else if (arg === "--print") { 64 | printDebug = true; 65 | } else if (arg === "--install-alias") { 66 | installAlias = true; 67 | } else if (arg === "--no-project-log") { 68 | console.log("Error: --no-project-log is no longer supported as project logs are the only option."); 69 | process.exit(1); 70 | } else { 71 | claudeArgs.push(arg); 72 | } 73 | } 74 | 75 | // Show help if requested but continue execution 76 | if (showHelpMessage) { 77 | showHelp(); 78 | } 79 | 80 | // Install alias if requested 81 | if (installAlias) { 82 | if (installClaudeAlias()) { 83 | if (claudeArgs.length === 0) { 84 | // If no other arguments were provided, exit after installing alias 85 | process.exit(0); 86 | } 87 | // Otherwise continue with normal operation 88 | } 89 | } 90 | 91 | // Get current working directory (project directory) 92 | const cwd = process.cwd(); 93 | const projectName = path.basename(cwd); 94 | 95 | // Generate session ID - used to maintain log file continuity across help usage 96 | const sessionId = process.env.CLAUDE_LOGGER_SESSION_ID || (() => { 97 | const now = new Date(); 98 | const timestamp = now.toISOString() 99 | .replace(/:/g, "-") 100 | .replace("T", "-") 101 | .replace(/\.\d+Z$/, ""); 102 | return `${timestamp}_${crypto.randomBytes(4).toString("hex")}`; 103 | })(); 104 | 105 | // Determine log directory - use specified log_dir if provided, otherwise use project directory 106 | const projectLogsDir = logDir ? logDir : path.join(cwd, "logs"); 107 | if (!fs.existsSync(projectLogsDir)) { 108 | fs.mkdirSync(projectLogsDir, { recursive: true }); 109 | } 110 | 111 | // Create log file with consistent naming across the session 112 | const logFile = path.join(projectLogsDir, `${projectName}_${sessionId}.jsonl`); 113 | 114 | // Check if this is the first time we're accessing this log file 115 | const isNewLogFile = !fs.existsSync(logFile); 116 | 117 | // Create or append to the log file 118 | try { 119 | // No need to initialize JSON Lines files - they start empty 120 | 121 | // Position the help message adjacent to user-facing logs by appending to existing log files 122 | if (showHelpMessage && !isNewLogFile) { 123 | // For JSON log, we don't need to do anything since it only captures API calls 124 | } 125 | 126 | if (printDebug) { 127 | console.log(`Logs will be saved to: ${logFile}`); 128 | } 129 | } catch (err) { 130 | console.error(`Error: Cannot write to log file: ${err.message}`); 131 | process.exit(1); 132 | } 133 | 134 | // Check if this is the first run in the project 135 | checkForFirstRunAndSuggestAlias(); 136 | 137 | console.log( 138 | `Claude Logger v${version} - Starting Claude` + 139 | (claudeArgs.includes("-p") ? " with prompt" : " in interactive mode") 140 | ); 141 | console.log(`Logs will be ${isNewLogFile ? 'created at' : 'appended to'}: ${logFile}`); 142 | 143 | // Run Claude with our direct_capture.js module loaded to intercept requests 144 | console.log("Initializing request capture..."); 145 | 146 | // Set up environment for direct_capture.js 147 | const existingNodeOptions = process.env.NODE_OPTIONS || ""; 148 | const captureEnv = { 149 | ...process.env, 150 | CLAUDE_API_LOG_FILE: logFile, 151 | CLAUDE_PROJECT_NAME: projectName, 152 | CLAUDE_DEBUG: printDebug ? "true" : "false", 153 | CLAUDE_LOGGER_VERSION: version, 154 | CLAUDE_LOGGER_SESSION_ID: sessionId, // Pass session ID for consistency across runs 155 | NODE_OPTIONS: `${existingNodeOptions} --require "${path.join(__dirname, "direct_capture.js")}"`.trim(), 156 | }; 157 | 158 | if (printDebug) { 159 | console.log("NODE_OPTIONS:", captureEnv.NODE_OPTIONS); 160 | } 161 | 162 | // Fix for cursor position - pass stdin directly to the claude process 163 | // and use stdio: 'inherit' for stdout so that terminal control sequences work properly 164 | const claudeProcess = spawn("claude", claudeArgs, { 165 | env: captureEnv, 166 | stdio: ["inherit", "inherit", "pipe"], // Pass stdin and stdout directly, only capture stderr 167 | }); 168 | 169 | // Capture and filter stderr (to hide debug messages when not in debug mode) 170 | let stderrBuffer = ""; 171 | claudeProcess.stderr.on("data", (data) => { 172 | const text = data.toString(); 173 | stderrBuffer += text; 174 | 175 | // Only print stderr if it's not debug output or if debug mode is enabled 176 | if ( 177 | printDebug || 178 | (!text.includes("DEBUG:") && 179 | !text.includes("intercepted") && 180 | !text.includes("Intercepted") && 181 | !text.includes("Environment Variables")) 182 | ) { 183 | process.stderr.write(data); 184 | } 185 | }); 186 | 187 | // Handle process exit 188 | claudeProcess.on("close", (code) => { 189 | // Give the child process time to flush logs 190 | setTimeout(() => { 191 | console.log(`Logs written to ${logFile}`); 192 | process.exit(code); 193 | }, 100); 194 | }); 195 | 196 | claudeProcess.on("error", (err) => { 197 | console.log(`Logs written to ${logFile}`); 198 | console.error(`Error running Claude: ${err.message}`); 199 | process.exit(1); 200 | }); 201 | 202 | /** 203 | * Installs the 'claude' alias to the user's shell configuration file 204 | * @returns {boolean} - True if alias was installed successfully, false otherwise 205 | */ 206 | function installClaudeAlias() { 207 | const homeDir = os.homedir(); 208 | 209 | // Detect shell type 210 | let shellType = ''; 211 | try { 212 | const shell = process.env.SHELL || ''; 213 | if (shell.includes('zsh')) shellType = 'zsh'; 214 | else if (shell.includes('bash')) shellType = 'bash'; 215 | else if (shell.includes('fish')) shellType = 'fish'; 216 | } catch (err) { 217 | // Default to bash if detection fails 218 | shellType = 'bash'; 219 | } 220 | 221 | // Determine appropriate config file 222 | let configFile = ''; 223 | let installCommands = ''; 224 | 225 | if (shellType === 'zsh') { 226 | configFile = path.join(homeDir, '.zshrc'); 227 | installCommands = "\n# Claude CLI alias for logging\nalias claude='claude-log'\n"; 228 | } else if (shellType === 'fish') { 229 | configFile = path.join(homeDir, '.config', 'fish', 'config.fish'); 230 | installCommands = "\n# Claude CLI alias for logging\nalias claude='claude-log'\n"; 231 | } else { 232 | // Default to bash 233 | configFile = path.join(homeDir, '.bashrc'); 234 | // Check if .bash_profile exists and prefer it on macOS 235 | const bashProfile = path.join(homeDir, '.bash_profile'); 236 | if (fs.existsSync(bashProfile) && process.platform === 'darwin') { 237 | configFile = bashProfile; 238 | } 239 | installCommands = "\n# Claude CLI alias for logging\nalias claude='claude-log'\n"; 240 | } 241 | 242 | try { 243 | // Create directory if it doesn't exist (particularly for Fish) 244 | if (shellType === 'fish') { 245 | const configDir = path.dirname(configFile); 246 | if (!fs.existsSync(configDir)) { 247 | fs.mkdirSync(configDir, { recursive: true }); 248 | } 249 | } 250 | 251 | // Check if file exists 252 | if (!fs.existsSync(configFile)) { 253 | // Create file if it doesn't exist 254 | fs.writeFileSync(configFile, installCommands); 255 | } else { 256 | // Check if alias already exists 257 | const content = fs.readFileSync(configFile, 'utf8'); 258 | if (!content.includes("alias claude='claude-log'")) { 259 | // Append alias to file 260 | fs.appendFileSync(configFile, installCommands); 261 | } else { 262 | console.log("The 'claude' alias is already installed in your shell configuration."); 263 | return true; 264 | } 265 | } 266 | 267 | // Output additional instructions for Fish shell 268 | if (shellType === 'fish') { 269 | console.log("\n✅ Alias installed! For Fish shell, you may also need to run:"); 270 | console.log(" funcsave claude"); 271 | } 272 | 273 | console.log(`\n✅ The 'claude' alias has been installed in ${configFile}`); 274 | console.log(" You'll need to restart your terminal or run the following to use it now:"); 275 | 276 | if (shellType === 'fish') { 277 | console.log(` source ${configFile}`); 278 | } else { 279 | console.log(` source ${configFile}`); 280 | } 281 | 282 | return true; 283 | } catch (err) { 284 | console.error(`Error installing alias: ${err.message}`); 285 | return false; 286 | } 287 | } 288 | 289 | /** 290 | * Checks if this is the first run in a project directory and suggests setting 291 | * up the claude alias if it's not already set up 292 | */ 293 | function checkForFirstRunAndSuggestAlias() { 294 | // Only suggest if this appears to be the first run in this project 295 | const isFirstProjectRun = !fs.existsSync(path.join(cwd, "logs")) || 296 | fs.readdirSync(path.join(cwd, "logs")).length === 0; 297 | 298 | if (!isFirstProjectRun) { 299 | return; 300 | } 301 | 302 | // Check for the alias in common shell config files 303 | const homeDir = os.homedir(); 304 | const configFiles = [ 305 | { path: path.join(homeDir, ".bashrc"), type: "bash" }, 306 | { path: path.join(homeDir, ".bash_profile"), type: "bash" }, 307 | { path: path.join(homeDir, ".zshrc"), type: "zsh" }, 308 | { path: path.join(homeDir, ".config", "fish", "config.fish"), type: "fish" } 309 | ]; 310 | 311 | let aliasFound = false; 312 | 313 | for (const config of configFiles) { 314 | if (fs.existsSync(config.path)) { 315 | try { 316 | const content = fs.readFileSync(config.path, "utf8"); 317 | if (content.includes("alias claude='claude-log'")) { 318 | aliasFound = true; 319 | break; 320 | } 321 | } catch (err) { 322 | // Skip if we can't read the file 323 | } 324 | } 325 | } 326 | 327 | if (!aliasFound) { 328 | console.log("\n========================================================"); 329 | console.log("💡 First time using claude-logger in this project!"); 330 | console.log(" For convenience, you can set up an alias in your shell:"); 331 | console.log(""); 332 | console.log(" For Bash/Zsh (add to .bashrc or .zshrc):"); 333 | console.log(" alias claude='claude-log'"); 334 | console.log(""); 335 | console.log(" For Fish shell:"); 336 | console.log(" alias claude='claude-log'"); 337 | console.log(" funcsave claude"); 338 | console.log("========================================================\n"); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /direct_capture.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Claude API Direct Capture Module 5 | * 6 | * This module intercepts HTTP/HTTPS requests made by the Claude CLI to capture 7 | * API calls to Anthropic. It can be used both as a library and standalone. 8 | */ 9 | 10 | const fs = require("fs"); 11 | const http = require("http"); 12 | const https = require("https"); 13 | const zlib = require("zlib"); 14 | const JsonLinesLogger = require("./jsonl_logger"); 15 | const anthropic_base_url = process.env.ANTHROPIC_BASE_URL || "anthropic.com"; 16 | 17 | // Configuration from environment variables 18 | const logFile = process.env.CLAUDE_API_LOG_FILE; 19 | const projectName = process.env.CLAUDE_PROJECT_NAME || "project"; 20 | const debugMode = process.env.CLAUDE_DEBUG === "true"; 21 | const version = process.env.CLAUDE_LOGGER_VERSION || "unknown"; 22 | 23 | // Initialize JSON Lines logger 24 | let jsonLinesLogger = null; 25 | if (logFile) { 26 | // Log file should already have .jsonl extension from claude_logger.js 27 | jsonLinesLogger = new JsonLinesLogger(logFile, { 28 | maxQueueSize: 10, // Smaller queue for more frequent writes 29 | flushInterval: 2000 30 | }); 31 | } 32 | 33 | // Log startup with version information 34 | debugLog(`Claude Logger v${version} started`); 35 | 36 | // Sensitive data patterns to redact 37 | const sensitivePatterns = [ 38 | // Header-related patterns 39 | { 40 | regex: /"x-api-key":\s*"([^"]+)"/g, 41 | replacement: '"x-api-key": "[REDACTED]"', 42 | }, 43 | { 44 | regex: /"Authorization":\s*"([^"]+)"/gi, 45 | replacement: '"Authorization": "[REDACTED]"', 46 | }, 47 | 48 | // User identification patterns 49 | // JSON format patterns (for request/response bodies) 50 | { regex: /"(user_id|account_id|account_uuid)":\s*"([^"]+)"/g, replacement: '"$1": "[REDACTED]"' }, 51 | // URL parameter patterns (for query strings) 52 | { regex: /(user_id|account_id|account_uuid)=([^&"\s]+)/g, replacement: '$1=[REDACTED]' }, 53 | 54 | // Organization patterns 55 | // JSON format patterns (for request/response bodies) 56 | { regex: /"(organization_id|org_id)":\s*"([^"]+)"/g, replacement: '"$1": "[REDACTED]"' }, 57 | // URL parameter patterns (for query strings) 58 | { regex: /(organization_id|org_id)=([^&"\s]+)/g, replacement: '$1=[REDACTED]' }, 59 | 60 | // Session-related patterns 61 | // JSON format pattern (for request/response bodies) 62 | { regex: /"session_id":\s*"([^"]+)"/g, replacement: '"session_id": "[REDACTED]"' }, 63 | // URL parameter pattern (for query strings) 64 | { regex: /session_id=([^&"\s]+)/g, replacement: 'session_id=[REDACTED]' }, 65 | 66 | // Token and authentication patterns 67 | // JSON format patterns (for request/response bodies) 68 | { regex: /"(token|refresh_token|access_token)":\s*"([^"]+)"/g, replacement: '"$1": "[REDACTED]"' }, 69 | 70 | // API key patterns 71 | // JSON format pattern (for request/response bodies) 72 | { regex: /"api_key":\s*"([^"]+)"/g, replacement: '"api_key": "[REDACTED]"' }, 73 | // URL parameter pattern (for query strings) 74 | { regex: /api_key=([^&"\s]+)/g, replacement: 'api_key=[REDACTED]' }, 75 | 76 | // UUID patterns (commonly used for identifiers) 77 | { regex: /\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/gi, replacement: '[UUID_REDACTED]' }, 78 | 79 | // Email patterns (for privacy) 80 | { regex: /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/g, replacement: '[EMAIL_REDACTED]' }, 81 | ]; 82 | 83 | // Debug logging function that only logs when debug mode is enabled 84 | function debugLog(message) { 85 | if (debugMode) { 86 | console.error(`DEBUG: ${message}`); 87 | if (jsonLinesLogger) { 88 | jsonLinesLogger.log({ 89 | debug: message 90 | }); 91 | } 92 | } 93 | } 94 | 95 | // Store original request methods before they're patched 96 | const originalHttpRequest = http.request; 97 | const originalHttpsRequest = https.request; 98 | 99 | debugLog("Patching http/https request methods"); 100 | 101 | // Counter for tracking requests 102 | let requestCounter = 0; 103 | 104 | // Store original fetch if it exists 105 | const originalFetch = global.fetch; 106 | 107 | // Track active connections for debugging 108 | const activeConnections = new Map(); 109 | 110 | // Patch the http.request method 111 | http.request = function () { 112 | debugLog("http.request intercepted"); 113 | 114 | // Get URL from options 115 | const options = arguments[0]; 116 | let url = ""; 117 | if (typeof options === "string") { 118 | url = options; 119 | } else if (options.href) { 120 | url = options.href; 121 | } else { 122 | url = `http://${options.hostname || options.host}${options.path || "/"}`; 123 | } 124 | 125 | // Call original and capture request object 126 | const req = originalHttpRequest.apply(this, arguments); 127 | 128 | // Track connection reuse 129 | if (req.socket) { 130 | const socketKey = `${req.socket.localAddress}:${req.socket.localPort}->${req.socket.remoteAddress}:${req.socket.remotePort}`; 131 | if (activeConnections.has(socketKey)) { 132 | debugLog(`Reusing existing connection: ${socketKey}`); 133 | activeConnections.set(socketKey, activeConnections.get(socketKey) + 1); 134 | } else { 135 | debugLog(`New connection established: ${socketKey}`); 136 | activeConnections.set(socketKey, 1); 137 | } 138 | } 139 | 140 | // Log request details 141 | logRequest("http", options, req, url); 142 | 143 | return req; 144 | }; 145 | 146 | // Patch the https.request method 147 | https.request = function () { 148 | debugLog("https.request intercepted"); 149 | 150 | // Get URL from options 151 | const options = arguments[0]; 152 | let url = ""; 153 | if (typeof options === "string") { 154 | url = options; 155 | } else if (options.href) { 156 | url = options.href; 157 | } else { 158 | url = `https://${options.hostname || options.host}${options.path || "/"}`; 159 | } 160 | 161 | debugLog(`Request details - URL: ${url}, Method: ${options.method || 'GET'}`); 162 | 163 | // Call original and capture request object 164 | const req = originalHttpsRequest.apply(this, arguments); 165 | 166 | // Track connection reuse for HTTPS 167 | req.on('socket', (socket) => { 168 | const socketKey = `${socket.localAddress || 'unknown'}:${socket.localPort || 'unknown'}->${socket.remoteAddress || options.hostname}:${socket.remotePort || options.port || 443}`; 169 | if (activeConnections.has(socketKey)) { 170 | const count = activeConnections.get(socketKey) + 1; 171 | debugLog(`HTTPS: Reusing existing connection: ${socketKey} (request #${count})`); 172 | activeConnections.set(socketKey, count); 173 | } else { 174 | debugLog(`HTTPS: New connection established: ${socketKey}`); 175 | activeConnections.set(socketKey, 1); 176 | } 177 | }); 178 | 179 | // Log request details 180 | logRequest("https", options, req, url); 181 | 182 | return req; 183 | }; 184 | 185 | // Redact sensitive information from a string 186 | function redactSensitiveInfo(str) { 187 | if (typeof str !== "string") return str; 188 | 189 | let redacted = str; 190 | for (const pattern of sensitivePatterns) { 191 | redacted = redacted.replace(pattern.regex, pattern.replacement); 192 | } 193 | return redacted; 194 | } 195 | 196 | // Log a request and its response 197 | function logRequest(protocol, options, req, url) { 198 | // Log all requests for debugging 199 | debugLog(`Intercepted ${protocol} request to: ${url}`); 200 | 201 | // Skip non-Claude API requests 202 | if (!url.includes("anthropic.com") && !url.includes(anthropic_base_url)) { 203 | debugLog(`Skipping non-Claude request: ${url}`); 204 | return; 205 | } 206 | 207 | requestCounter++; 208 | const requestId = `req-${Date.now()}-${requestCounter}`; 209 | debugLog(`Found Claude API request #${requestCounter} (ID: ${requestId}) to: ${url}`); 210 | debugLog(`Logging API request #${requestCounter}`); 211 | 212 | // Get current datetime for the log entry 213 | const timestamp = new Date().toISOString(); 214 | 215 | // Create log data object with request/response structure 216 | const logEntry = { 217 | requestId: requestId, 218 | request: { 219 | timestamp: timestamp, 220 | protocol: protocol, 221 | url: url, 222 | }, 223 | response: null, // Will be populated when response is received 224 | }; 225 | 226 | // Store requestId on the request object for later reference 227 | req._logRequestId = requestId; 228 | req._logEntry = logEntry; 229 | 230 | // Add project metadata 231 | logEntry.project = { 232 | name: projectName, 233 | timestamp: timestamp, 234 | version: version 235 | }; 236 | 237 | // Don't log yet - wait for response 238 | 239 | // Add method and headers if available (with sensitive data redacted) 240 | if (typeof options === "object") { 241 | logEntry.request.method = options.method || "GET"; 242 | 243 | // Copy headers (avoid deep copy which might fail on non-plain objects) 244 | if (options.headers) { 245 | logEntry.request.headers = {}; 246 | try { 247 | // Copy headers safely 248 | for (const key in options.headers) { 249 | if (options.headers.hasOwnProperty(key)) { 250 | const lowerKey = key.toLowerCase(); 251 | if (lowerKey === "x-api-key" || lowerKey === "authorization") { 252 | logEntry.request.headers[key] = "[REDACTED]"; 253 | } else { 254 | logEntry.request.headers[key] = String(options.headers[key]); 255 | } 256 | } 257 | } 258 | } catch (e) { 259 | debugLog(`Error copying headers: ${e.message}`); 260 | logEntry.request.headers = { error: "Failed to copy headers" }; 261 | } 262 | } else { 263 | logEntry.request.headers = {}; 264 | } 265 | } 266 | 267 | // Capture request body by monkey patching write and end 268 | const originalWrite = req.write; 269 | const originalEnd = req.end; 270 | let requestBody = []; 271 | 272 | req.write = function (chunk) { 273 | if (chunk) { 274 | // Ensure chunk is a Buffer 275 | requestBody.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); 276 | } 277 | return originalWrite.apply(this, arguments); 278 | }; 279 | 280 | req.end = function (chunk) { 281 | if (chunk) { 282 | // Ensure chunk is a Buffer 283 | requestBody.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); 284 | } 285 | 286 | // Add body to log data if we captured any 287 | if (requestBody.length > 0) { 288 | try { 289 | const body = Buffer.concat(requestBody).toString(); 290 | try { 291 | // Parse the body as JSON and redact sensitive fields 292 | const bodyObj = JSON.parse(body); 293 | 294 | // Redact user_id if present 295 | if (bodyObj.user_id) { 296 | bodyObj.user_id = "[REDACTED]"; 297 | } 298 | 299 | logEntry.request.body = bodyObj; 300 | } catch (e) { 301 | // If not valid JSON, store as string with redacted sensitive info 302 | logEntry.request.body = redactSensitiveInfo(body); 303 | } 304 | } catch (e) { 305 | logEntry.request.body = "Error capturing body: " + e.message; 306 | } 307 | } 308 | 309 | // Call original method 310 | return originalEnd.apply(this, arguments); 311 | }; 312 | 313 | // Capture response data 314 | req.on("response", (res) => { 315 | const responseData = { 316 | timestamp: new Date().toISOString(), 317 | statusCode: res.statusCode, 318 | headers: res.headers, 319 | }; 320 | 321 | // Collect response body chunks 322 | const responseChunks = []; 323 | let responseTimeout = null; 324 | let isStreamingResponse = false; 325 | let streamingLogWritten = false; 326 | 327 | // Handle response stream errors 328 | res.on('error', (error) => { 329 | debugLog(`Response stream error for ${requestId}: ${error.message}`); 330 | responseData.error = `Response error: ${error.message}`; 331 | 332 | // Clear timeout on error 333 | if (responseTimeout) { 334 | clearTimeout(responseTimeout); 335 | } 336 | 337 | // Log the error if not already logged 338 | if (!streamingLogWritten && jsonLinesLogger) { 339 | logEntry.response = responseData; 340 | jsonLinesLogger.log(logEntry).catch(err => { 341 | console.error('Error logging response error:', err); 342 | }); 343 | } 344 | }); 345 | 346 | // Check if this is a streaming response 347 | if (res.headers["content-type"] && res.headers["content-type"].includes("text/event-stream")) { 348 | isStreamingResponse = true; 349 | debugLog("Detected streaming (SSE) response"); 350 | 351 | // For streaming responses, log the request immediately with a placeholder response 352 | responseData.streaming = true; 353 | responseData.streamStarted = new Date().toISOString(); 354 | logEntry.response = responseData; 355 | 356 | // Write the initial request/response pair immediately for streaming 357 | if (jsonLinesLogger) { 358 | jsonLinesLogger.log(logEntry).catch(err => { 359 | console.error('Error logging streaming request:', err); 360 | }); 361 | } 362 | streamingLogWritten = true; 363 | debugLog(`Logged streaming request ${req._logRequestId}`); 364 | } 365 | 366 | // Set a timeout for response collection 367 | // Use longer timeout for streaming responses (5 minutes vs 30 seconds) 368 | const timeoutDuration = isStreamingResponse ? 300000 : 30000; 369 | responseTimeout = setTimeout(() => { 370 | debugLog(`Response timeout reached after ${timeoutDuration/1000}s, logging partial response`); 371 | responseData.timeout = true; 372 | responseData.partialBody = responseChunks.length > 0 ? 373 | `Partial response collected (${responseChunks.length} chunks, ${Buffer.concat(responseChunks).length} bytes)` : 374 | "No response data collected"; 375 | 376 | // Don't overwrite if already written for streaming 377 | if (!streamingLogWritten) { 378 | logEntry.response = responseData; 379 | // Log the timeout response 380 | if (jsonLinesLogger) { 381 | jsonLinesLogger.log(logEntry).catch(err => { 382 | console.error('Error logging timeout request:', err); 383 | }); 384 | } 385 | } 386 | }, timeoutDuration); 387 | 388 | // Important: Set up data handler immediately to ensure stream flows 389 | // and doesn't cause backpressure that could lead to timeouts 390 | res.on("data", (chunk) => { 391 | responseChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); 392 | debugLog(`Response chunk received: ${chunk.length} bytes`); 393 | 394 | // For very large responses (>10MB), consider dropping old chunks to prevent memory issues 395 | const totalSize = responseChunks.reduce((sum, c) => sum + c.length, 0); 396 | if (totalSize > 10 * 1024 * 1024) { // 10MB limit 397 | debugLog(`Warning: Large response detected (${totalSize} bytes), may impact performance`); 398 | } 399 | }); 400 | 401 | // Process complete response 402 | res.on("end", () => { 403 | // Clear timeout since response completed 404 | if (responseTimeout) { 405 | clearTimeout(responseTimeout); 406 | } 407 | let responseBody = ""; 408 | try { 409 | const buffer = Buffer.concat(responseChunks); 410 | 411 | // Check for compression by content-encoding header or by detecting magic bytes 412 | const isGzip = res.headers["content-encoding"] === "gzip" || 413 | (buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b); 414 | 415 | if (isGzip) { 416 | responseBody = zlib.gunzipSync(buffer).toString(); 417 | } else if (res.headers["content-encoding"] === "br") { 418 | responseBody = zlib.brotliDecompressSync(buffer).toString(); 419 | } else if (res.headers["content-encoding"] === "deflate") { 420 | responseBody = zlib.inflateSync(buffer).toString(); 421 | } else { 422 | responseBody = buffer.toString(); 423 | } 424 | } catch (e) { 425 | responseData.bodyError = "Error processing response body: " + e.message; 426 | 427 | // Only update and write if not already written for streaming 428 | if (!streamingLogWritten) { 429 | logEntry.response = responseData; 430 | 431 | // Log the error response 432 | if (jsonLinesLogger) { 433 | jsonLinesLogger.log(logEntry).catch(err => { 434 | console.error('Error logging response error:', err); 435 | }); 436 | } 437 | } 438 | return; 439 | } 440 | 441 | if ( 442 | res.headers["content-type"] && 443 | res.headers["content-type"].includes("text/event-stream") 444 | ) { 445 | debugLog(`Processing SSE response, total size: ${responseBody.length} bytes`); 446 | // Parse SSE responses 447 | try { 448 | responseData.events = responseBody 449 | .split("\n\n") 450 | .filter((event) => event.trim()) 451 | .map((event) => { 452 | const lines = event.split("\n"); 453 | const result = {}; 454 | for (const line of lines) { 455 | if (line.startsWith("event: ")) { 456 | result.event = line.substring(7); 457 | } else if (line.startsWith("data: ")) { 458 | try { 459 | const data = JSON.parse(line.substring(6)); 460 | 461 | // Redact user_id in SSE events if present 462 | if (data.user_id) { 463 | data.user_id = "[REDACTED]"; 464 | } 465 | 466 | result.data = data; 467 | } catch (e) { 468 | result.data = line.substring(6); 469 | } 470 | } 471 | } 472 | return result; 473 | }); 474 | } catch (e) { 475 | responseData.sseParseError = e.message; 476 | } 477 | } else { 478 | // For non-SSE responses, try to parse as JSON 479 | try { 480 | const parsedBody = JSON.parse(responseBody); 481 | 482 | // Redact sensitive information 483 | if (parsedBody.user_id) { 484 | parsedBody.user_id = "[REDACTED]"; 485 | } 486 | 487 | // Redact account information 488 | if (parsedBody.account) { 489 | if (parsedBody.account.uuid) parsedBody.account.uuid = "[REDACTED]"; 490 | if (parsedBody.account.email) parsedBody.account.email = "[EMAIL_REDACTED]"; 491 | if (parsedBody.account.full_name) parsedBody.account.full_name = "[REDACTED]"; 492 | } 493 | 494 | // Redact organization information 495 | if (parsedBody.organization) { 496 | if (parsedBody.organization.uuid) parsedBody.organization.uuid = "[REDACTED]"; 497 | if (parsedBody.organization.name) parsedBody.organization.name = "[REDACTED]"; 498 | } 499 | 500 | responseData.body = parsedBody; 501 | } catch (e) { 502 | // If not JSON, store the body (with sensitive data redacted) 503 | responseData.body = redactSensitiveInfo(responseBody); 504 | } 505 | } 506 | 507 | // Only update response if not already written for streaming 508 | if (!streamingLogWritten) { 509 | logEntry.response = responseData; 510 | 511 | // Log the complete request/response pair 512 | if (jsonLinesLogger) { 513 | jsonLinesLogger.log(logEntry).catch(err => { 514 | console.error('Error logging request:', err); 515 | }); 516 | } 517 | 518 | const reqId = req._logRequestId || 'unknown'; 519 | debugLog(`Response complete for request ${reqId}`); 520 | debugLog(`Response had ${responseChunks.length} chunks, total size: ${Buffer.concat(responseChunks).length} bytes`); 521 | } else { 522 | debugLog(`Streaming response ended for request ${req._logRequestId}, already logged`); 523 | } 524 | }); 525 | 526 | // Also track response errors 527 | res.on("error", (error) => { 528 | const reqId = req._logRequestId || 'unknown'; 529 | debugLog(`Response error for request ${reqId}: ${error.message}`); 530 | responseData.error = error.message; 531 | 532 | // Only update and write if not already written for streaming 533 | if (!streamingLogWritten) { 534 | logEntry.response = responseData; 535 | 536 | // Log the complete request/response pair (even with error) 537 | if (jsonLinesLogger) { 538 | jsonLinesLogger.log(logEntry).catch(err => { 539 | console.error('Error logging request:', err); 540 | }); 541 | } 542 | 543 | // Clear timeout on error 544 | if (responseTimeout) { 545 | clearTimeout(responseTimeout); 546 | } 547 | 548 | // Log the error response 549 | if (jsonLinesLogger) { 550 | jsonLinesLogger.log(logEntry).catch(err => { 551 | console.error('Error logging error response:', err); 552 | }); 553 | } 554 | } 555 | }); 556 | }); 557 | 558 | // Track request errors 559 | req.on("error", (error) => { 560 | debugLog(`Request error for ${requestId}: ${error.message}`); 561 | logEntry.request.error = error.message; 562 | 563 | // Check if this is an abort-related error 564 | if (error.code === 'ECONNRESET' && error.message.includes('aborted')) { 565 | debugLog(`Request aborted via ECONNRESET for ${requestId}`); 566 | } 567 | 568 | // Create minimal response entry for failed requests 569 | if (!logEntry.response) { 570 | logEntry.response = { 571 | timestamp: new Date().toISOString(), 572 | error: `Request failed: ${error.message}` 573 | }; 574 | } 575 | 576 | // Log the connection error 577 | if (jsonLinesLogger && req._logEntry) { 578 | jsonLinesLogger.log(req._logEntry).catch(err => { 579 | console.error('Error logging connection error:', err); 580 | }); 581 | } 582 | }); 583 | 584 | // Handle socket errors to prevent crashes 585 | req.on('socket', (socket) => { 586 | // Store original listeners count to avoid interfering with existing handlers 587 | const originalErrorListenerCount = socket.listenerCount('error'); 588 | const originalCloseListenerCount = socket.listenerCount('close'); 589 | 590 | // Only add our listeners if we haven't already 591 | if (!socket._loggerListenersAdded) { 592 | socket._loggerListenersAdded = true; 593 | 594 | // Add our error handler without removing existing ones 595 | socket.prependListener('error', (error) => { 596 | debugLog(`Socket error for ${requestId}: ${error.message}`); 597 | // Log but don't prevent other handlers from running 598 | if (error.code === 'ECONNRESET' || error.code === 'EPIPE') { 599 | debugLog(`Socket closed unexpectedly for ${requestId}`); 600 | } 601 | }); 602 | 603 | socket.prependListener('close', (hadError) => { 604 | if (hadError) { 605 | debugLog(`Socket closed with error for ${requestId}`); 606 | } 607 | }); 608 | } 609 | }); 610 | 611 | // Handle request abort/destroy 612 | // Note: 'abort' event is deprecated in newer Node versions, but we'll handle both for compatibility 613 | const handleAbort = (reason) => { 614 | debugLog(`Request aborted for ${requestId}: ${reason}`); 615 | if (!logEntry.response) { 616 | logEntry.response = { 617 | timestamp: new Date().toISOString(), 618 | error: 'Request aborted - this may be due to Claude CLI timeout during compaction or other long operations', 619 | abortReason: reason || 'Client cancelled request' 620 | }; 621 | if (jsonLinesLogger) { 622 | jsonLinesLogger.log(logEntry).catch(err => { 623 | console.error('Error logging aborted request:', err); 624 | }); 625 | } 626 | } 627 | }; 628 | 629 | req.on('abort', () => handleAbort('abort event')); 630 | 631 | req.on('close', () => { 632 | debugLog(`Request closed for ${requestId}`); 633 | // If request closed without response, log it 634 | if (!logEntry.response && !req._headerSent) { 635 | logEntry.response = { 636 | timestamp: new Date().toISOString(), 637 | error: 'Request closed without response' 638 | }; 639 | if (jsonLinesLogger) { 640 | jsonLinesLogger.log(logEntry).catch(err => { 641 | console.error('Error logging closed request:', err); 642 | }); 643 | } 644 | } 645 | }); 646 | } 647 | 648 | 649 | // JSON Lines logger handles its own periodic flushing and memory management 650 | 651 | /** 652 | * Set up process signal handlers to ensure logs are written before exit 653 | */ 654 | function setupSignalHandlers() { 655 | // Handle normal exit 656 | process.on("exit", () => { 657 | // JSON Lines logger will handle flushing on exit 658 | if (jsonLinesLogger) { 659 | jsonLinesLogger.close(); 660 | } 661 | debugLog(`Claude logger exiting gracefully`); 662 | }); 663 | 664 | // Handle termination signals 665 | const handleSignal = (signal) => { 666 | return () => { 667 | debugLog(`Capture process received ${signal}, flushing logs and exiting`); 668 | 669 | // JSON Lines logger will handle flushing 670 | if (jsonLinesLogger) { 671 | jsonLinesLogger.close(); 672 | } 673 | 674 | process.exit(0); // Exit with success code 675 | }; 676 | }; 677 | 678 | // Set up handlers for common termination signals 679 | process.on("SIGTERM", handleSignal("SIGTERM")); 680 | process.on("SIGINT", handleSignal("SIGINT")); 681 | 682 | // Handle uncaught exceptions - attempt to save logs before crashing 683 | process.on("uncaughtException", (err) => { 684 | console.error(`FATAL: Uncaught exception: ${err.message}`); 685 | console.error(err.stack); 686 | // JSON Lines logger will handle flushing 687 | if (jsonLinesLogger) { 688 | jsonLinesLogger.close(); 689 | } 690 | process.exit(1); // Exit with error code 691 | }); 692 | } 693 | 694 | // Set up signal handlers 695 | setupSignalHandlers(); 696 | 697 | // Set up global error handlers to prevent crashes from network errors 698 | process.on('uncaughtException', (error) => { 699 | // Check if this is a network-related error we can safely ignore 700 | const isNetworkError = 701 | error.code === 'ECONNRESET' || 702 | error.code === 'EPIPE' || 703 | error.code === 'ENOTFOUND' || 704 | error.code === 'ETIMEDOUT' || 705 | error.code === 'ECONNREFUSED' || 706 | (error.message && ( 707 | error.message.includes('socket hang up') || 708 | error.message.includes('read ECONNRESET') || 709 | error.message.includes('write EPIPE') 710 | )); 711 | 712 | if (isNetworkError) { 713 | console.error('Network error in claude-logger (continuing):', error.message); 714 | debugLog(`Network error caught at process level: ${error.code || error.message}`); 715 | // Don't crash on network errors - these are expected in networked applications 716 | } else { 717 | // Log and re-emit non-network errors 718 | console.error('Uncaught exception in claude-logger:', error); 719 | // Remove our handler and re-emit to let the process handle it 720 | process.removeAllListeners('uncaughtException'); 721 | process.emit('uncaughtException', error); 722 | } 723 | }); 724 | 725 | process.on('unhandledRejection', (reason, promise) => { 726 | // Similar handling for promise rejections 727 | const isNetworkError = reason && ( 728 | reason.code === 'ECONNRESET' || 729 | reason.code === 'EPIPE' || 730 | reason.code === 'ENOTFOUND' || 731 | reason.code === 'ETIMEDOUT' || 732 | reason.code === 'ECONNREFUSED' || 733 | (reason.message && ( 734 | reason.message.includes('socket hang up') || 735 | reason.message.includes('read ECONNRESET') || 736 | reason.message.includes('write EPIPE') 737 | )) 738 | ); 739 | 740 | if (isNetworkError) { 741 | console.error('Network error in promise (continuing):', reason.message || reason); 742 | debugLog('Network error caught in promise rejection'); 743 | } else { 744 | // Log but don't re-throw promise rejections 745 | console.error('Unhandled rejection in claude-logger:', reason); 746 | } 747 | }); 748 | 749 | // Log when module is loaded 750 | debugLog("Direct capture module loaded"); 751 | debugLog(`Process PID: ${process.pid}`); 752 | debugLog(`Parent PID: ${process.ppid}`); 753 | debugLog(`Log file: ${logFile}`); 754 | debugLog(`Process title: ${process.title}`); 755 | debugLog(`Exec path: ${process.execPath}`); 756 | 757 | // If running directly and not as a module 758 | if (require.main === module) { 759 | debugLog("Direct capture module loaded, HTTP/HTTPS interception active"); 760 | console.log(`Claude API Logger v${version} started - capturing requests to anthropic.com`); 761 | } else { 762 | debugLog("Module loaded via require()"); 763 | } 764 | 765 | // Patch fetch if it exists 766 | if (typeof global.fetch === 'function') { 767 | debugLog("Patching global.fetch"); 768 | 769 | global.fetch = async function(url, options = {}) { 770 | const urlString = typeof url === 'string' ? url : url.toString(); 771 | 772 | debugLog(`Intercepted fetch request to: ${urlString}`); 773 | 774 | // Check if this is a Claude API request 775 | if (urlString.includes('anthropic.com') || urlString.includes(anthropic_base_url)) { 776 | requestCounter++; 777 | const requestId = `req-${Date.now()}-${requestCounter}`; 778 | debugLog(`Found Claude API fetch request #${requestCounter} (ID: ${requestId}) to: ${urlString}`); 779 | 780 | const timestamp = new Date().toISOString(); 781 | 782 | // Create log entry 783 | const logEntry = { 784 | requestId: requestId, 785 | request: { 786 | timestamp: timestamp, 787 | protocol: 'fetch', 788 | url: urlString, 789 | method: (options.method || 'GET').toUpperCase(), 790 | headers: options.headers || {} 791 | }, 792 | response: null, 793 | project: { 794 | name: projectName, 795 | timestamp: timestamp, 796 | version: version 797 | } 798 | }; 799 | 800 | // Redact authorization header 801 | if (logEntry.request.headers['authorization']) { 802 | logEntry.request.headers['authorization'] = '[REDACTED]'; 803 | } 804 | if (logEntry.request.headers['Authorization']) { 805 | logEntry.request.headers['Authorization'] = '[REDACTED]'; 806 | } 807 | 808 | // Add body if present 809 | if (options.body) { 810 | try { 811 | logEntry.request.body = typeof options.body === 'string' ? 812 | JSON.parse(options.body) : options.body; 813 | } catch (e) { 814 | logEntry.request.body = options.body; 815 | } 816 | } 817 | 818 | // Don't log yet - wait for response 819 | 820 | try { 821 | // Call original fetch 822 | const response = await originalFetch(url, options); 823 | 824 | // Clone response to read it without consuming 825 | const responseClone = response.clone(); 826 | 827 | // Log response 828 | const responseData = { 829 | timestamp: new Date().toISOString(), 830 | statusCode: response.status, 831 | headers: {} 832 | }; 833 | 834 | // Copy headers 835 | response.headers.forEach((value, key) => { 836 | responseData.headers[key] = value; 837 | }); 838 | 839 | try { 840 | const responseBody = await responseClone.text(); 841 | const contentType = response.headers.get('content-type'); 842 | 843 | if (contentType && contentType.includes('application/json')) { 844 | try { 845 | const parsedBody = JSON.parse(responseBody); 846 | 847 | // Redact sensitive information 848 | if (parsedBody.user_id) parsedBody.user_id = "[REDACTED]"; 849 | if (parsedBody.account) { 850 | if (parsedBody.account.uuid) parsedBody.account.uuid = "[REDACTED]"; 851 | if (parsedBody.account.email) parsedBody.account.email = "[EMAIL_REDACTED]"; 852 | if (parsedBody.account.full_name) parsedBody.account.full_name = "[REDACTED]"; 853 | } 854 | if (parsedBody.organization) { 855 | if (parsedBody.organization.uuid) parsedBody.organization.uuid = "[REDACTED]"; 856 | if (parsedBody.organization.name) parsedBody.organization.name = "[REDACTED]"; 857 | } 858 | 859 | responseData.body = parsedBody; 860 | } catch (e) { 861 | responseData.body = redactSensitiveInfo(responseBody); 862 | } 863 | } else if (contentType && contentType.includes('text/event-stream')) { 864 | // Handle SSE responses 865 | responseData.body = responseBody; 866 | responseData.streaming = true; 867 | } else { 868 | responseData.body = redactSensitiveInfo(responseBody); 869 | } 870 | } catch (e) { 871 | responseData.bodyError = 'Error reading response body: ' + e.message; 872 | } 873 | 874 | logEntry.response = responseData; 875 | 876 | // Log the complete request/response pair 877 | if (jsonLinesLogger) { 878 | jsonLinesLogger.log(logEntry).catch(err => { 879 | console.error('Error logging fetch request:', err); 880 | }); 881 | } 882 | debugLog(`Fetch response complete for request ${requestId}`); 883 | 884 | return response; 885 | } catch (error) { 886 | // Log error 887 | logEntry.response = { 888 | timestamp: new Date().toISOString(), 889 | error: error.message 890 | }; 891 | 892 | // Log the error response 893 | if (jsonLinesLogger) { 894 | jsonLinesLogger.log(logEntry).catch(err => { 895 | console.error('Error logging fetch error:', err); 896 | }); 897 | } 898 | 899 | throw error; 900 | } 901 | } 902 | 903 | // For non-Claude requests, just pass through 904 | return originalFetch(url, options); 905 | }; 906 | } else { 907 | debugLog("No global.fetch found, skipping fetch patching"); 908 | } 909 | 910 | /** 911 | * Module exports for the Claude API Direct Capture 912 | * @module claude-logger/direct-capture 913 | */ 914 | module.exports = { 915 | // Expose the JSON Lines logger 916 | jsonLinesLogger, 917 | 918 | // Core functions 919 | redactSensitiveInfo, 920 | 921 | // Log file paths 922 | logFile, 923 | 924 | // Configuration 925 | debugMode 926 | }; 927 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useRef } from 'react' 2 | 3 | const LogFileViewer = () => { 4 | const [logData, setLogData] = useState([]) 5 | const [expandedItems, setExpandedItems] = useState(new Set()) 6 | const [isDragOver, setIsDragOver] = useState(false) 7 | const [currentFileName, setCurrentFileName] = useState('') 8 | const [selectedModels, setSelectedModels] = useState(new Set()) 9 | const fileInputRef = useRef(null) 10 | 11 | // Parse timestamp string to Date object 12 | const parseTimestamp = (timestampStr) => { 13 | return new Date(timestampStr.replace('Z', '+00:00')) 14 | } 15 | 16 | // Extract API data from log entry 17 | const extractApiData = (logEntry) => { 18 | const request = logEntry.request || {} 19 | const response = logEntry.response || {} 20 | 21 | // Check if this is an API request to anyrouter.top (Claude API) 22 | if (!request.url?.includes('anyrouter.top')) { 23 | return null 24 | } 25 | 26 | const requestBody = request.body || {} 27 | const responseBody = response.body || '' 28 | 29 | // Parse streaming response for token usage and assistant message 30 | let inputTokens = null 31 | let outputTokens = null 32 | let cachedInputTokens = null 33 | let cacheCreationInputTokens = null 34 | let assistantMessage = '' 35 | let toolCalls = [] 36 | let currentToolCall = null 37 | 38 | if (typeof responseBody === 'string' && responseBody.includes('message_start')) { 39 | const lines = responseBody.split('\n') 40 | for (const line of lines) { 41 | if (line.startsWith('data: ')) { 42 | try { 43 | const dataStr = line.slice(6).trim() 44 | if (!dataStr || dataStr.startsWith('{"type": "ping"}')) { 45 | continue 46 | } 47 | const data = JSON.parse(dataStr) 48 | 49 | // Extract usage info from message_start event 50 | if (data.type === 'message_start' && data.message) { 51 | const usage = data.message.usage || {} 52 | inputTokens = usage.input_tokens 53 | outputTokens = usage.output_tokens 54 | cachedInputTokens = usage.cache_read_input_tokens 55 | cacheCreationInputTokens = usage.cache_creation_input_tokens 56 | } 57 | 58 | // Extract message content from text deltas 59 | else if (data.type === 'content_block_delta' && data.delta?.text) { 60 | assistantMessage += data.delta.text 61 | } 62 | 63 | // Handle tool use content blocks 64 | else if (data.type === 'content_block_start' && data.content_block?.type === 'tool_use') { 65 | currentToolCall = { 66 | id: data.content_block.id, 67 | name: data.content_block.name, 68 | input: '' 69 | } 70 | } 71 | 72 | // Handle tool use partial JSON 73 | else if (data.type === 'content_block_delta' && data.delta?.partial_json && currentToolCall) { 74 | currentToolCall.input += data.delta.partial_json 75 | } 76 | 77 | // Complete tool call 78 | else if (data.type === 'content_block_stop' && currentToolCall) { 79 | try { 80 | currentToolCall.input = JSON.parse(currentToolCall.input) 81 | } catch (e) { 82 | // Keep as string if parsing fails 83 | } 84 | toolCalls.push(currentToolCall) 85 | currentToolCall = null 86 | } 87 | 88 | // Update output tokens from message_delta 89 | else if (data.type === 'message_delta' && data.usage) { 90 | outputTokens = data.usage.output_tokens 91 | } 92 | 93 | } catch (e) { 94 | continue 95 | } 96 | } 97 | } 98 | } 99 | 100 | const messages = requestBody.messages || [] 101 | const userMessages = messages.filter(msg => msg.role === 'user') 102 | 103 | let lastUserMessage = '' 104 | if (userMessages.length > 0) { 105 | const lastMsg = userMessages[userMessages.length - 1] 106 | const content = lastMsg.content || '' 107 | if (typeof content === 'string') { 108 | lastUserMessage = content 109 | } else if (Array.isArray(content)) { 110 | const textParts = content 111 | .filter(part => typeof part === 'object' && part.type === 'text') 112 | .map(part => part.text || '') 113 | lastUserMessage = textParts.join(' ') 114 | } 115 | } 116 | 117 | const systemPrompt = requestBody.system || [] 118 | const tools = requestBody.tools || [] 119 | 120 | // Parse system prompt array 121 | let parsedSystemPrompts = [] 122 | if (Array.isArray(systemPrompt)) { 123 | parsedSystemPrompts = systemPrompt.map(prompt => ({ 124 | text: prompt.text || '', 125 | isCached: !!(prompt.cache_control && prompt.cache_control.type === 'ephemeral') 126 | })) 127 | } else if (typeof systemPrompt === 'string') { 128 | parsedSystemPrompts = [{ text: systemPrompt, isCached: false }] 129 | } 130 | 131 | // Parse messages with content parts and caching 132 | const parsedMessages = messages.map(msg => { 133 | let contentParts = [] 134 | if (typeof msg.content === 'string') { 135 | contentParts = [{ type: 'text', text: msg.content, isCached: false }] 136 | } else if (Array.isArray(msg.content)) { 137 | contentParts = msg.content.map(part => ({ 138 | type: part.type || 'text', 139 | text: part.text || '', 140 | tool_use_id: part.tool_use_id || '', 141 | name: part.name || '', 142 | input: part.input || '', 143 | content: part.content || '', 144 | is_error: part.is_error || false, 145 | isCached: !!(part.cache_control && part.cache_control.type === 'ephemeral') 146 | })) 147 | } 148 | return { 149 | role: msg.role, 150 | contentParts 151 | } 152 | }) 153 | 154 | const requestTime = request.timestamp ? parseTimestamp(request.timestamp) : null 155 | const responseTime = response.timestamp ? parseTimestamp(response.timestamp) : null 156 | const responseTimeMs = (requestTime && responseTime) 157 | ? responseTime.getTime() - requestTime.getTime() 158 | : null 159 | 160 | return { 161 | id: logEntry.requestId, 162 | timestamp: requestTime, 163 | model: requestBody.model, 164 | maxTokens: requestBody.max_tokens, 165 | temperature: requestBody.temperature, 166 | messageCount: messages.length, 167 | systemPrompts: parsedSystemPrompts, 168 | messages: parsedMessages, 169 | tools: tools, 170 | lastUserMessage, 171 | assistantMessage, 172 | toolCalls, 173 | inputTokens, 174 | outputTokens, 175 | cachedInputTokens, 176 | cacheCreationInputTokens, 177 | responseStatus: response.statusCode, 178 | responseTimeMs, 179 | toolsAvailable: tools.length, 180 | streaming: requestBody.stream || false, 181 | rawRequest: request, 182 | rawResponse: response 183 | } 184 | } 185 | 186 | // Parse log file 187 | const parseLogFile = async (file) => { 188 | const text = await file.text() 189 | const lines = text.trim().split('\n') 190 | const apiRequests = [] 191 | 192 | for (const line of lines) { 193 | if (!line.trim()) continue 194 | 195 | try { 196 | const logEntry = JSON.parse(line) 197 | const apiData = extractApiData(logEntry) 198 | if (apiData) { 199 | apiRequests.push(apiData) 200 | } 201 | } catch (e) { 202 | console.warn('Could not parse line:', e) 203 | } 204 | } 205 | 206 | return apiRequests.sort((a, b) => 207 | (a.timestamp?.getTime() || 0) - (b.timestamp?.getTime() || 0) 208 | ) 209 | } 210 | 211 | const handleDrop = useCallback(async (e) => { 212 | e.preventDefault() 213 | setIsDragOver(false) 214 | 215 | const files = Array.from(e.dataTransfer.files) 216 | const logFile = files.find(f => f.name.endsWith('.jsonl') || f.name.includes('claude-code-logger')) 217 | 218 | if (logFile) { 219 | try { 220 | const data = await parseLogFile(logFile) 221 | setLogData(data) 222 | setCurrentFileName(logFile.name) 223 | } catch (error) { 224 | console.error('Error parsing log file:', error) 225 | alert('Error parsing log file. Please ensure it\'s a valid JSONL file.') 226 | } 227 | } else { 228 | alert('Please drop a valid Claude Code Logger .jsonl file') 229 | } 230 | }, []) 231 | 232 | const handleDragOver = useCallback((e) => { 233 | e.preventDefault() 234 | setIsDragOver(true) 235 | }, []) 236 | 237 | const handleDragLeave = useCallback((e) => { 238 | e.preventDefault() 239 | setIsDragOver(false) 240 | }, []) 241 | 242 | const handleFileSelect = useCallback(async (e) => { 243 | const files = Array.from(e.target.files) 244 | const logFile = files.find(f => f.name.endsWith('.jsonl') || f.name.includes('claude-code-logger')) 245 | 246 | if (logFile) { 247 | try { 248 | const data = await parseLogFile(logFile) 249 | setLogData(data) 250 | setCurrentFileName(logFile.name) 251 | } catch (error) { 252 | console.error('Error parsing log file:', error) 253 | alert('Error parsing log file. Please ensure it\'s a valid JSONL file.') 254 | } 255 | } else { 256 | alert('Please select a valid Claude Code Logger .jsonl file') 257 | } 258 | }, []) 259 | 260 | const handleClickToSelect = useCallback(() => { 261 | fileInputRef.current?.click() 262 | }, []) 263 | 264 | const toggleExpanded = (id) => { 265 | const newExpanded = new Set(expandedItems) 266 | if (newExpanded.has(id)) { 267 | newExpanded.delete(id) 268 | } else { 269 | newExpanded.add(id) 270 | } 271 | setExpandedItems(newExpanded) 272 | } 273 | 274 | // Get preview text for collapsed view 275 | const getSystemPromptPreview = (systemPrompts) => { 276 | if (!systemPrompts || systemPrompts.length === 0) return '' 277 | return systemPrompts[0].text ? truncateText(systemPrompts[0].text, 100) : '' 278 | } 279 | 280 | const getFirstUserMessagePreview = (messages) => { 281 | if (!messages || messages.length === 0) return '' 282 | const userMessage = messages.find(msg => msg.role === 'user') 283 | if (!userMessage || !userMessage.contentParts || userMessage.contentParts.length === 0) return '' 284 | const textPart = userMessage.contentParts.find(part => part.type === 'text') 285 | return textPart ? truncateText(textPart.text, 100) : '' 286 | } 287 | 288 | const getResponsePreview = (assistantMessage) => { 289 | return assistantMessage ? truncateText(assistantMessage, 100) : '' 290 | } 291 | 292 | const formatTokens = (tokens) => { 293 | return tokens ? tokens.toLocaleString() : 'N/A' 294 | } 295 | 296 | const formatTime = (timestamp) => { 297 | if (!timestamp) return 'N/A' 298 | return timestamp.toLocaleString('en-US', { 299 | year: 'numeric', 300 | month: '2-digit', 301 | day: '2-digit', 302 | hour: '2-digit', 303 | minute: '2-digit', 304 | second: '2-digit', 305 | fractionalSecondDigits: 3, 306 | hour12: false 307 | }) 308 | } 309 | 310 | const formatDuration = (ms) => { 311 | return ms ? `${ms.toFixed(0)}ms` : 'N/A' 312 | } 313 | 314 | const truncateText = (text, maxLength = 100) => { 315 | if (!text) return 'N/A' 316 | return text.length > maxLength ? text.substring(0, maxLength) + '...' : text 317 | } 318 | 319 | // Get unique models from log data 320 | const getUniqueModels = () => { 321 | const models = new Set() 322 | logData.forEach(item => { 323 | if (item.model) { 324 | models.add(item.model) 325 | } 326 | }) 327 | return Array.from(models).sort() 328 | } 329 | 330 | // Filter log data based on selected models 331 | const getFilteredData = () => { 332 | if (selectedModels.size === 0) { 333 | return logData 334 | } 335 | return logData.filter(item => selectedModels.has(item.model)) 336 | } 337 | 338 | // Toggle model selection 339 | const toggleModelSelection = (model) => { 340 | const newSelection = new Set(selectedModels) 341 | if (newSelection.has(model)) { 342 | newSelection.delete(model) 343 | } else { 344 | newSelection.add(model) 345 | } 346 | setSelectedModels(newSelection) 347 | } 348 | 349 | // Component for displaying system prompts with caching indicators 350 | const SystemPromptsDisplay = ({ systemPrompts }) => { 351 | if (!systemPrompts || systemPrompts.length === 0) { 352 | return
No system prompt
353 | } 354 | 355 | return ( 356 |
357 | {systemPrompts.map((prompt, index) => ( 358 |
359 |
360 | System Prompt {index + 1} 361 | {prompt.isCached && ( 362 |
363 | 364 | CACHED 365 |
366 | )} 367 |
368 |
 369 |               {prompt.text || 'N/A'}
 370 |             
371 |
372 | ))} 373 |
374 | ) 375 | } 376 | 377 | // Component for displaying tools with proper JSON schema formatting 378 | const ToolsDisplay = ({ tools }) => { 379 | if (!tools || tools.length === 0) { 380 | return
No tools available
381 | } 382 | 383 | return ( 384 |
385 | {tools.map((tool, index) => ( 386 | 387 | ))} 388 |
389 | ) 390 | } 391 | 392 | // Individual tool card component 393 | const ToolCard = ({ tool, index }) => { 394 | const [isSchemaExpanded, setIsSchemaExpanded] = useState(false) 395 | const [isToolExpanded, setIsToolExpanded] = useState(false) 396 | 397 | return ( 398 |
399 | {/* Tool Header - Always Visible */} 400 |
setIsToolExpanded(!isToolExpanded)} 403 | > 404 |
405 |
406 |
{tool.name}
407 | Tool 408 |
409 |
410 | {tool.input_schema && ( 411 | 412 | {Object.keys(tool.input_schema.properties || {}).length} properties 413 | 414 | )} 415 | 416 | {isToolExpanded ? '▼' : '▶'} 417 | 418 |
419 |
420 | 421 | {/* Collapsed Description Preview */} 422 | {!isToolExpanded && tool.description && ( 423 |

424 | {tool.description.length > 150 ? tool.description.substring(0, 150) + '...' : tool.description} 425 |

426 | )} 427 |
428 | 429 | {/* Expanded Content */} 430 | {isToolExpanded && ( 431 |
432 | {/* Full Description Section - Always Full When Expanded */} 433 | {tool.description && ( 434 |
435 |
436 |
437 | 📝 Description 438 |
439 |
440 |

441 | {tool.description} 442 |

443 |
444 | )} 445 | 446 | {/* Schema Section */} 447 | {tool.input_schema && ( 448 |
449 |
setIsSchemaExpanded(!isSchemaExpanded)} 452 | > 453 |
454 |
455 | 🔧 Input Schema 456 |
457 | 458 | {isSchemaExpanded ? 'Click to hide' : 'Click to show details'} 459 | 460 |
461 |
462 | {isSchemaExpanded && ( 463 |
464 | 465 |
466 | )} 467 |
468 | )} 469 |
470 | )} 471 |
472 | ) 473 | } 474 | 475 | // Custom JSON Schema Viewer component 476 | const JsonSchemaViewer = ({ schema }) => { 477 | if (!schema) return null 478 | 479 | const renderProperty = (key, prop, required = false, level = 0) => { 480 | const indent = level * 16 481 | const isRequired = required 482 | 483 | return ( 484 |
485 |
486 | 489 | {key} 490 | {isRequired && *} 491 | 492 | {prop.type && ( 493 | 494 | {Array.isArray(prop.type) ? prop.type.join(' | ') : prop.type} 495 | 496 | )} 497 | {prop.enum && ( 498 | 499 | enum 500 | 501 | )} 502 | {prop.default !== undefined && ( 503 | 504 | default: {String(prop.default)} 505 | 506 | )} 507 |
508 | 509 | {prop.description && ( 510 |

511 | {prop.description} 512 |

513 | )} 514 | 515 | {prop.enum && ( 516 |
517 | Allowed values: 518 |
519 | {prop.enum.map((value, idx) => ( 520 | 521 | {String(value)} 522 | 523 | ))} 524 |
525 |
526 | )} 527 | 528 | {prop.properties && ( 529 |
530 | Properties: 531 | {Object.entries(prop.properties).map(([subKey, subProp]) => 532 | renderProperty( 533 | subKey, 534 | subProp, 535 | prop.required?.includes(subKey), 536 | level + 1 537 | ) 538 | )} 539 |
540 | )} 541 | 542 | {prop.items && ( 543 |
544 | Array items: 545 | {renderProperty('items', prop.items, false, level + 1)} 546 |
547 | )} 548 |
549 | ) 550 | } 551 | 552 | return ( 553 |
554 |
555 |
556 |
Schema Details
557 | {schema.type && ( 558 | 559 | {schema.type} 560 | 561 | )} 562 |
563 | {schema.description && ( 564 |

{schema.description}

565 | )} 566 |
567 | 568 | {schema.properties && ( 569 |
570 |
571 | Properties 572 |
573 |
574 | {Object.entries(schema.properties).map(([key, prop]) => 575 | renderProperty(key, prop, schema.required?.includes(key)) 576 | )} 577 |
578 |
579 | )} 580 | 581 | {schema.required && schema.required.length > 0 && ( 582 |
583 |
Required Properties
584 |
585 | {schema.required.map((req, idx) => ( 586 | 587 | {req} 588 | 589 | ))} 590 |
591 |
592 | )} 593 | 594 |
595 | Show raw schema 596 |
 597 |             {JSON.stringify(schema, null, 2)}
 598 |           
599 |
600 |
601 | ) 602 | } 603 | 604 | // Component for displaying messages with role-based formatting and caching 605 | const MessagesDisplay = ({ messages }) => { 606 | if (!messages || messages.length === 0) { 607 | return
No messages
608 | } 609 | 610 | const getRoleColor = (role) => { 611 | switch (role) { 612 | case 'user': return 'border-blue-500 bg-blue-50' 613 | case 'assistant': return 'border-green-500 bg-green-50' 614 | case 'system': return 'border-purple-500 bg-purple-50' 615 | default: return 'border-gray-500 bg-gray-50' 616 | } 617 | } 618 | 619 | const getRoleIcon = (role) => { 620 | switch (role) { 621 | case 'user': return '👤' 622 | case 'assistant': return '🤖' 623 | case 'system': return '⚙️' 624 | default: return '📝' 625 | } 626 | } 627 | 628 | return ( 629 |
630 | {messages.map((message, messageIndex) => ( 631 |
632 |
633 |
634 | {getRoleIcon(message.role)} 635 | {message.role} 636 | {message.contentParts.some(part => part.isCached) && ( 637 |
638 | 639 | HAS CACHED CONTENT 640 |
641 | )} 642 |
643 |
644 |
645 | {message.contentParts.map((part, partIndex) => ( 646 |
647 |
648 | 649 | {part.type === 'text' ? '📝 Text Content' : 650 | part.type === 'tool_use' ? '🔧 Tool Use' : 651 | part.type === 'tool_result' ? '📊 Tool Result' : 652 | `📎 ${part.type}`} 653 | 654 |
655 | {part.type === 'tool_use' && part.name && ( 656 | 657 | {part.name} 658 | 659 | )} 660 | {part.type === 'tool_result' && part.tool_use_id && ( 661 | 662 | ID: {part.tool_use_id} 663 | 664 | )} 665 | {part.type === 'tool_result' && part.is_error && ( 666 | 667 | ERROR 668 | 669 | )} 670 | {part.isCached && ( 671 |
672 | 673 | CACHED 674 |
675 | )} 676 |
677 |
678 |
679 | {part.type === 'text' && ( 680 |
 681 |                         {part.text || 'N/A'}
 682 |                       
683 | )} 684 | {part.type === 'tool_use' && ( 685 |
686 |
687 |
Tool Name:
688 | {part.name || 'N/A'} 689 |
690 |
691 |
Input:
692 |
 693 |                             {typeof part.input === 'object' ? JSON.stringify(part.input, null, 2) : part.input || 'N/A'}
 694 |                           
695 |
696 |
697 | )} 698 | {part.type === 'tool_result' && ( 699 |
700 |
701 |
Tool Use ID:
702 | {part.tool_use_id || 'N/A'} 703 |
704 |
705 |
Result:
706 |
 709 |                             {part.content || 'N/A'}
 710 |                           
711 |
712 |
713 | )} 714 |
715 |
716 | ))} 717 |
718 |
719 | ))} 720 |
721 | ) 722 | } 723 | 724 | // Component for displaying tool calls made by the assistant 725 | const ToolCallsDisplay = ({ toolCalls }) => { 726 | if (!toolCalls || toolCalls.length === 0) { 727 | return
No tool calls
728 | } 729 | 730 | return ( 731 |
732 | {toolCalls.map((toolCall, index) => ( 733 |
734 |
735 | 🔧 736 |
{toolCall.name}
737 | 738 | {toolCall.id} 739 | 740 |
741 | 742 |
743 |
Input:
744 |
 745 |                 {typeof toolCall.input === 'object' 
 746 |                   ? JSON.stringify(toolCall.input, null, 2) 
 747 |                   : toolCall.input || 'N/A'}
 748 |               
749 |
750 |
751 | ))} 752 |
753 | ) 754 | } 755 | 756 | const resetToFileSelection = () => { 757 | setLogData([]) 758 | setCurrentFileName('') 759 | setExpandedItems(new Set()) 760 | setSelectedModels(new Set()) 761 | } 762 | 763 | const scrollToTop = () => { 764 | window.scrollTo({ top: 0, behavior: 'smooth' }) 765 | } 766 | 767 | return ( 768 |
769 |

Claude Code Logger Viewer

770 | 771 | {currentFileName && ( 772 |
773 |
774 |
775 | 📄 776 |
777 |

Currently viewing:

778 |

{currentFileName}

779 |
780 |
781 | 787 |
788 |
789 | )} 790 | 791 | {logData.length === 0 ? ( 792 |
801 |
802 |

Drop a Claude Code Logger .jsonl file here

803 |

or click to select a file

804 | Files should be named like: claude-code-logger_*.jsonl 805 |
806 | 813 |
814 | ) : ( 815 |
816 |
817 |

Summary

818 |

Total requests: {logData.length}

819 |

Total input tokens: {formatTokens(logData.reduce((sum, item) => sum + (item.inputTokens || 0), 0))}

820 |

Total output tokens: {formatTokens(logData.reduce((sum, item) => sum + (item.outputTokens || 0), 0))}

821 |
822 | 823 | {/* Model Filter Section */} 824 | {getUniqueModels().length > 1 && ( 825 |
826 |

Filter by Models

827 |
828 | {getUniqueModels().map(model => ( 829 | 841 | ))} 842 |
843 | {selectedModels.size > 0 && ( 844 |
845 | 846 | Showing {getFilteredData().length} of {logData.length} requests 847 | 848 | 854 |
855 | )} 856 |
857 | )} 858 | 859 |
860 | {getFilteredData().map((item, index) => ( 861 |
862 |
toggleExpanded(item.id)} 865 | > 866 | {/* Header row with request number, model, tokens, time */} 867 |
868 |
869 | 870 | #{index + 1} of {getFilteredData().length} 871 | 872 | {item.model} 873 | 874 | {formatTokens(item.inputTokens)} / {formatTokens(item.outputTokens)} tokens 875 | {item.cachedInputTokens > 0 && ( 876 | ({formatTokens(item.cachedInputTokens)} cached) 877 | )} 878 | {item.systemPrompts?.some(p => p.isCached) && ( 879 | (⚡ cached) 880 | )} 881 | 882 | {formatTime(item.timestamp)} 883 | {formatDuration(item.responseTimeMs)} 884 |
885 | 886 | {expandedItems.has(item.id) ? '▼' : '▶'} 887 | 888 |
889 | 890 | {/* Preview rows when collapsed */} 891 | {!expandedItems.has(item.id) && ( 892 |
893 | {getSystemPromptPreview(item.systemPrompts) && ( 894 |
895 | System: 896 | {getSystemPromptPreview(item.systemPrompts)} 897 |
898 | )} 899 | {getFirstUserMessagePreview(item.messages) && ( 900 |
901 | User: 902 | {getFirstUserMessagePreview(item.messages)} 903 |
904 | )} 905 | {getResponsePreview(item.assistantMessage) && ( 906 |
907 | Response: 908 | {getResponsePreview(item.assistantMessage)} 909 |
910 | )} 911 |
912 | )} 913 |
914 | 915 | {expandedItems.has(item.id) && ( 916 |
917 | {/* Token Usage Section - First */} 918 |
919 |

Token Usage

920 |
921 |
922 |

Input Tokens

923 |

{formatTokens(item.inputTokens)}

924 |
925 |
926 |

Output Tokens

927 |

{formatTokens(item.outputTokens)}

928 |
929 |
930 |

Cached Input

931 |

{formatTokens(item.cachedInputTokens)}

932 |
933 |
934 |

Cache Creation

935 |

{formatTokens(item.cacheCreationInputTokens)}

936 |
937 |
938 |
939 | 940 | {/* Content Sections */} 941 |
942 | {/* System Prompts - Second */} 943 |
944 |

945 | System Prompts 946 |

947 | 948 |
949 | 950 | {/* Messages and Assistant Response - Third */} 951 |
952 |

953 | Messages ({item.messageCount}) 954 |

955 | 956 |
957 | 958 |
959 |

Assistant Response

960 | 961 | {/* Tool Calls */} 962 | {item.toolCalls && item.toolCalls.length > 0 && ( 963 |
964 |
Tool Calls ({item.toolCalls.length}):
965 | 966 |
967 | )} 968 | 969 | {/* Text Response */} 970 | {item.assistantMessage && ( 971 |
972 |
Text Response:
973 |
 974 |                               {item.assistantMessage}
 975 |                             
976 |
977 | )} 978 | 979 | {!item.assistantMessage && (!item.toolCalls || item.toolCalls.length === 0) && ( 980 |
No response content
981 | )} 982 |
983 | 984 | {/* Tools - Fourth */} 985 |
986 |

987 | Tools ({item.toolsAvailable}) 988 |

989 | 990 |
991 | 992 | {/* Request Details - Fifth */} 993 |
994 |

Request Details

995 |
996 |
997 |

Request ID: {item.id}

998 |

Model: {item.model}

999 |

Temperature: {item.temperature}

1000 |
1001 |
1002 |

Max Tokens: {item.maxTokens}

1003 |

Streaming: {item.streaming ? 'Yes' : 'No'}

1004 |

Response Status: {item.responseStatus}

1005 |
1006 |
1007 |
1008 |
1009 | 1010 |
1011 | 1012 | Raw Request/Response Data 1013 | 1014 |
1015 |
1016 |
Request
1017 |
1018 |                             {JSON.stringify(item.rawRequest, null, 2)}
1019 |                           
1020 |
1021 |
1022 |
Response
1023 |
1024 |                             {JSON.stringify(item.rawResponse, null, 2)}
1025 |                           
1026 |
1027 |
1028 |
1029 |
1030 | )} 1031 |
1032 | ))} 1033 |
1034 |
1035 | )} 1036 | 1037 | {/* Scroll to Top Button */} 1038 | {logData.length > 0 && ( 1039 | 1048 | )} 1049 |
1050 | ) 1051 | } 1052 | 1053 | export default LogFileViewer -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | react: 12 | specifier: ^18.3.1 13 | version: 18.3.1 14 | react-dom: 15 | specifier: ^18.3.1 16 | version: 18.3.1(react@18.3.1) 17 | devDependencies: 18 | '@types/react': 19 | specifier: ^18.3.3 20 | version: 18.3.23 21 | '@types/react-dom': 22 | specifier: ^18.3.0 23 | version: 18.3.7(@types/react@18.3.23) 24 | '@vitejs/plugin-react': 25 | specifier: ^4.3.1 26 | version: 4.6.0(vite@5.4.19) 27 | autoprefixer: 28 | specifier: ^10.4.16 29 | version: 10.4.21(postcss@8.5.6) 30 | postcss: 31 | specifier: ^8.4.32 32 | version: 8.5.6 33 | tailwindcss: 34 | specifier: ^3.4.0 35 | version: 3.4.17 36 | vite: 37 | specifier: ^5.3.1 38 | version: 5.4.19 39 | 40 | packages: 41 | 42 | '@alloc/quick-lru@5.2.0': 43 | resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 44 | engines: {node: '>=10'} 45 | 46 | '@ampproject/remapping@2.3.0': 47 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 48 | engines: {node: '>=6.0.0'} 49 | 50 | '@babel/code-frame@7.27.1': 51 | resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 52 | engines: {node: '>=6.9.0'} 53 | 54 | '@babel/compat-data@7.28.0': 55 | resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} 56 | engines: {node: '>=6.9.0'} 57 | 58 | '@babel/core@7.28.0': 59 | resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} 60 | engines: {node: '>=6.9.0'} 61 | 62 | '@babel/generator@7.28.0': 63 | resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} 64 | engines: {node: '>=6.9.0'} 65 | 66 | '@babel/helper-compilation-targets@7.27.2': 67 | resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} 68 | engines: {node: '>=6.9.0'} 69 | 70 | '@babel/helper-globals@7.28.0': 71 | resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 72 | engines: {node: '>=6.9.0'} 73 | 74 | '@babel/helper-module-imports@7.27.1': 75 | resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} 76 | engines: {node: '>=6.9.0'} 77 | 78 | '@babel/helper-module-transforms@7.27.3': 79 | resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} 80 | engines: {node: '>=6.9.0'} 81 | peerDependencies: 82 | '@babel/core': ^7.0.0 83 | 84 | '@babel/helper-plugin-utils@7.27.1': 85 | resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} 86 | engines: {node: '>=6.9.0'} 87 | 88 | '@babel/helper-string-parser@7.27.1': 89 | resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 90 | engines: {node: '>=6.9.0'} 91 | 92 | '@babel/helper-validator-identifier@7.27.1': 93 | resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} 94 | engines: {node: '>=6.9.0'} 95 | 96 | '@babel/helper-validator-option@7.27.1': 97 | resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} 98 | engines: {node: '>=6.9.0'} 99 | 100 | '@babel/helpers@7.27.6': 101 | resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} 102 | engines: {node: '>=6.9.0'} 103 | 104 | '@babel/parser@7.28.0': 105 | resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} 106 | engines: {node: '>=6.0.0'} 107 | hasBin: true 108 | 109 | '@babel/plugin-transform-react-jsx-self@7.27.1': 110 | resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} 111 | engines: {node: '>=6.9.0'} 112 | peerDependencies: 113 | '@babel/core': ^7.0.0-0 114 | 115 | '@babel/plugin-transform-react-jsx-source@7.27.1': 116 | resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} 117 | engines: {node: '>=6.9.0'} 118 | peerDependencies: 119 | '@babel/core': ^7.0.0-0 120 | 121 | '@babel/template@7.27.2': 122 | resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} 123 | engines: {node: '>=6.9.0'} 124 | 125 | '@babel/traverse@7.28.0': 126 | resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} 127 | engines: {node: '>=6.9.0'} 128 | 129 | '@babel/types@7.28.0': 130 | resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} 131 | engines: {node: '>=6.9.0'} 132 | 133 | '@esbuild/aix-ppc64@0.21.5': 134 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 135 | engines: {node: '>=12'} 136 | cpu: [ppc64] 137 | os: [aix] 138 | 139 | '@esbuild/android-arm64@0.21.5': 140 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 141 | engines: {node: '>=12'} 142 | cpu: [arm64] 143 | os: [android] 144 | 145 | '@esbuild/android-arm@0.21.5': 146 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 147 | engines: {node: '>=12'} 148 | cpu: [arm] 149 | os: [android] 150 | 151 | '@esbuild/android-x64@0.21.5': 152 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 153 | engines: {node: '>=12'} 154 | cpu: [x64] 155 | os: [android] 156 | 157 | '@esbuild/darwin-arm64@0.21.5': 158 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 159 | engines: {node: '>=12'} 160 | cpu: [arm64] 161 | os: [darwin] 162 | 163 | '@esbuild/darwin-x64@0.21.5': 164 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 165 | engines: {node: '>=12'} 166 | cpu: [x64] 167 | os: [darwin] 168 | 169 | '@esbuild/freebsd-arm64@0.21.5': 170 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 171 | engines: {node: '>=12'} 172 | cpu: [arm64] 173 | os: [freebsd] 174 | 175 | '@esbuild/freebsd-x64@0.21.5': 176 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 177 | engines: {node: '>=12'} 178 | cpu: [x64] 179 | os: [freebsd] 180 | 181 | '@esbuild/linux-arm64@0.21.5': 182 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 183 | engines: {node: '>=12'} 184 | cpu: [arm64] 185 | os: [linux] 186 | 187 | '@esbuild/linux-arm@0.21.5': 188 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 189 | engines: {node: '>=12'} 190 | cpu: [arm] 191 | os: [linux] 192 | 193 | '@esbuild/linux-ia32@0.21.5': 194 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 195 | engines: {node: '>=12'} 196 | cpu: [ia32] 197 | os: [linux] 198 | 199 | '@esbuild/linux-loong64@0.21.5': 200 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 201 | engines: {node: '>=12'} 202 | cpu: [loong64] 203 | os: [linux] 204 | 205 | '@esbuild/linux-mips64el@0.21.5': 206 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 207 | engines: {node: '>=12'} 208 | cpu: [mips64el] 209 | os: [linux] 210 | 211 | '@esbuild/linux-ppc64@0.21.5': 212 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 213 | engines: {node: '>=12'} 214 | cpu: [ppc64] 215 | os: [linux] 216 | 217 | '@esbuild/linux-riscv64@0.21.5': 218 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 219 | engines: {node: '>=12'} 220 | cpu: [riscv64] 221 | os: [linux] 222 | 223 | '@esbuild/linux-s390x@0.21.5': 224 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 225 | engines: {node: '>=12'} 226 | cpu: [s390x] 227 | os: [linux] 228 | 229 | '@esbuild/linux-x64@0.21.5': 230 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 231 | engines: {node: '>=12'} 232 | cpu: [x64] 233 | os: [linux] 234 | 235 | '@esbuild/netbsd-x64@0.21.5': 236 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 237 | engines: {node: '>=12'} 238 | cpu: [x64] 239 | os: [netbsd] 240 | 241 | '@esbuild/openbsd-x64@0.21.5': 242 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 243 | engines: {node: '>=12'} 244 | cpu: [x64] 245 | os: [openbsd] 246 | 247 | '@esbuild/sunos-x64@0.21.5': 248 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 249 | engines: {node: '>=12'} 250 | cpu: [x64] 251 | os: [sunos] 252 | 253 | '@esbuild/win32-arm64@0.21.5': 254 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 255 | engines: {node: '>=12'} 256 | cpu: [arm64] 257 | os: [win32] 258 | 259 | '@esbuild/win32-ia32@0.21.5': 260 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 261 | engines: {node: '>=12'} 262 | cpu: [ia32] 263 | os: [win32] 264 | 265 | '@esbuild/win32-x64@0.21.5': 266 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 267 | engines: {node: '>=12'} 268 | cpu: [x64] 269 | os: [win32] 270 | 271 | '@isaacs/cliui@8.0.2': 272 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 273 | engines: {node: '>=12'} 274 | 275 | '@jridgewell/gen-mapping@0.3.12': 276 | resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} 277 | 278 | '@jridgewell/resolve-uri@3.1.2': 279 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 280 | engines: {node: '>=6.0.0'} 281 | 282 | '@jridgewell/sourcemap-codec@1.5.4': 283 | resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} 284 | 285 | '@jridgewell/trace-mapping@0.3.29': 286 | resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} 287 | 288 | '@nodelib/fs.scandir@2.1.5': 289 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 290 | engines: {node: '>= 8'} 291 | 292 | '@nodelib/fs.stat@2.0.5': 293 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 294 | engines: {node: '>= 8'} 295 | 296 | '@nodelib/fs.walk@1.2.8': 297 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 298 | engines: {node: '>= 8'} 299 | 300 | '@pkgjs/parseargs@0.11.0': 301 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 302 | engines: {node: '>=14'} 303 | 304 | '@rolldown/pluginutils@1.0.0-beta.19': 305 | resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==} 306 | 307 | '@rollup/rollup-android-arm-eabi@4.44.2': 308 | resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} 309 | cpu: [arm] 310 | os: [android] 311 | 312 | '@rollup/rollup-android-arm64@4.44.2': 313 | resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} 314 | cpu: [arm64] 315 | os: [android] 316 | 317 | '@rollup/rollup-darwin-arm64@4.44.2': 318 | resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} 319 | cpu: [arm64] 320 | os: [darwin] 321 | 322 | '@rollup/rollup-darwin-x64@4.44.2': 323 | resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} 324 | cpu: [x64] 325 | os: [darwin] 326 | 327 | '@rollup/rollup-freebsd-arm64@4.44.2': 328 | resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} 329 | cpu: [arm64] 330 | os: [freebsd] 331 | 332 | '@rollup/rollup-freebsd-x64@4.44.2': 333 | resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} 334 | cpu: [x64] 335 | os: [freebsd] 336 | 337 | '@rollup/rollup-linux-arm-gnueabihf@4.44.2': 338 | resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} 339 | cpu: [arm] 340 | os: [linux] 341 | 342 | '@rollup/rollup-linux-arm-musleabihf@4.44.2': 343 | resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} 344 | cpu: [arm] 345 | os: [linux] 346 | 347 | '@rollup/rollup-linux-arm64-gnu@4.44.2': 348 | resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} 349 | cpu: [arm64] 350 | os: [linux] 351 | 352 | '@rollup/rollup-linux-arm64-musl@4.44.2': 353 | resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} 354 | cpu: [arm64] 355 | os: [linux] 356 | 357 | '@rollup/rollup-linux-loongarch64-gnu@4.44.2': 358 | resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} 359 | cpu: [loong64] 360 | os: [linux] 361 | 362 | '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': 363 | resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} 364 | cpu: [ppc64] 365 | os: [linux] 366 | 367 | '@rollup/rollup-linux-riscv64-gnu@4.44.2': 368 | resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} 369 | cpu: [riscv64] 370 | os: [linux] 371 | 372 | '@rollup/rollup-linux-riscv64-musl@4.44.2': 373 | resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} 374 | cpu: [riscv64] 375 | os: [linux] 376 | 377 | '@rollup/rollup-linux-s390x-gnu@4.44.2': 378 | resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} 379 | cpu: [s390x] 380 | os: [linux] 381 | 382 | '@rollup/rollup-linux-x64-gnu@4.44.2': 383 | resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} 384 | cpu: [x64] 385 | os: [linux] 386 | 387 | '@rollup/rollup-linux-x64-musl@4.44.2': 388 | resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} 389 | cpu: [x64] 390 | os: [linux] 391 | 392 | '@rollup/rollup-win32-arm64-msvc@4.44.2': 393 | resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} 394 | cpu: [arm64] 395 | os: [win32] 396 | 397 | '@rollup/rollup-win32-ia32-msvc@4.44.2': 398 | resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} 399 | cpu: [ia32] 400 | os: [win32] 401 | 402 | '@rollup/rollup-win32-x64-msvc@4.44.2': 403 | resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} 404 | cpu: [x64] 405 | os: [win32] 406 | 407 | '@types/babel__core@7.20.5': 408 | resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 409 | 410 | '@types/babel__generator@7.27.0': 411 | resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} 412 | 413 | '@types/babel__template@7.4.4': 414 | resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 415 | 416 | '@types/babel__traverse@7.20.7': 417 | resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} 418 | 419 | '@types/estree@1.0.8': 420 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 421 | 422 | '@types/prop-types@15.7.15': 423 | resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} 424 | 425 | '@types/react-dom@18.3.7': 426 | resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} 427 | peerDependencies: 428 | '@types/react': ^18.0.0 429 | 430 | '@types/react@18.3.23': 431 | resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} 432 | 433 | '@vitejs/plugin-react@4.6.0': 434 | resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} 435 | engines: {node: ^14.18.0 || >=16.0.0} 436 | peerDependencies: 437 | vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 438 | 439 | ansi-regex@5.0.1: 440 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 441 | engines: {node: '>=8'} 442 | 443 | ansi-regex@6.1.0: 444 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 445 | engines: {node: '>=12'} 446 | 447 | ansi-styles@4.3.0: 448 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 449 | engines: {node: '>=8'} 450 | 451 | ansi-styles@6.2.1: 452 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 453 | engines: {node: '>=12'} 454 | 455 | any-promise@1.3.0: 456 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 457 | 458 | anymatch@3.1.3: 459 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 460 | engines: {node: '>= 8'} 461 | 462 | arg@5.0.2: 463 | resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 464 | 465 | autoprefixer@10.4.21: 466 | resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} 467 | engines: {node: ^10 || ^12 || >=14} 468 | hasBin: true 469 | peerDependencies: 470 | postcss: ^8.1.0 471 | 472 | balanced-match@1.0.2: 473 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 474 | 475 | binary-extensions@2.3.0: 476 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 477 | engines: {node: '>=8'} 478 | 479 | brace-expansion@2.0.2: 480 | resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 481 | 482 | braces@3.0.3: 483 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 484 | engines: {node: '>=8'} 485 | 486 | browserslist@4.25.1: 487 | resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} 488 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 489 | hasBin: true 490 | 491 | camelcase-css@2.0.1: 492 | resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 493 | engines: {node: '>= 6'} 494 | 495 | caniuse-lite@1.0.30001726: 496 | resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} 497 | 498 | chokidar@3.6.0: 499 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 500 | engines: {node: '>= 8.10.0'} 501 | 502 | color-convert@2.0.1: 503 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 504 | engines: {node: '>=7.0.0'} 505 | 506 | color-name@1.1.4: 507 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 508 | 509 | commander@4.1.1: 510 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 511 | engines: {node: '>= 6'} 512 | 513 | convert-source-map@2.0.0: 514 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 515 | 516 | cross-spawn@7.0.6: 517 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 518 | engines: {node: '>= 8'} 519 | 520 | cssesc@3.0.0: 521 | resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 522 | engines: {node: '>=4'} 523 | hasBin: true 524 | 525 | csstype@3.1.3: 526 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 527 | 528 | debug@4.4.1: 529 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 530 | engines: {node: '>=6.0'} 531 | peerDependencies: 532 | supports-color: '*' 533 | peerDependenciesMeta: 534 | supports-color: 535 | optional: true 536 | 537 | didyoumean@1.2.2: 538 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 539 | 540 | dlv@1.1.3: 541 | resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 542 | 543 | eastasianwidth@0.2.0: 544 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 545 | 546 | electron-to-chromium@1.5.179: 547 | resolution: {integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==} 548 | 549 | emoji-regex@8.0.0: 550 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 551 | 552 | emoji-regex@9.2.2: 553 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 554 | 555 | esbuild@0.21.5: 556 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 557 | engines: {node: '>=12'} 558 | hasBin: true 559 | 560 | escalade@3.2.0: 561 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 562 | engines: {node: '>=6'} 563 | 564 | fast-glob@3.3.3: 565 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 566 | engines: {node: '>=8.6.0'} 567 | 568 | fastq@1.19.1: 569 | resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 570 | 571 | fill-range@7.1.1: 572 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 573 | engines: {node: '>=8'} 574 | 575 | foreground-child@3.3.1: 576 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 577 | engines: {node: '>=14'} 578 | 579 | fraction.js@4.3.7: 580 | resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 581 | 582 | fsevents@2.3.3: 583 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 584 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 585 | os: [darwin] 586 | 587 | function-bind@1.1.2: 588 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 589 | 590 | gensync@1.0.0-beta.2: 591 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 592 | engines: {node: '>=6.9.0'} 593 | 594 | glob-parent@5.1.2: 595 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 596 | engines: {node: '>= 6'} 597 | 598 | glob-parent@6.0.2: 599 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 600 | engines: {node: '>=10.13.0'} 601 | 602 | glob@10.4.5: 603 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 604 | hasBin: true 605 | 606 | hasown@2.0.2: 607 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 608 | engines: {node: '>= 0.4'} 609 | 610 | is-binary-path@2.1.0: 611 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 612 | engines: {node: '>=8'} 613 | 614 | is-core-module@2.16.1: 615 | resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 616 | engines: {node: '>= 0.4'} 617 | 618 | is-extglob@2.1.1: 619 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 620 | engines: {node: '>=0.10.0'} 621 | 622 | is-fullwidth-code-point@3.0.0: 623 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 624 | engines: {node: '>=8'} 625 | 626 | is-glob@4.0.3: 627 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 628 | engines: {node: '>=0.10.0'} 629 | 630 | is-number@7.0.0: 631 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 632 | engines: {node: '>=0.12.0'} 633 | 634 | isexe@2.0.0: 635 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 636 | 637 | jackspeak@3.4.3: 638 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 639 | 640 | jiti@1.21.7: 641 | resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} 642 | hasBin: true 643 | 644 | js-tokens@4.0.0: 645 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 646 | 647 | jsesc@3.1.0: 648 | resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 649 | engines: {node: '>=6'} 650 | hasBin: true 651 | 652 | json5@2.2.3: 653 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 654 | engines: {node: '>=6'} 655 | hasBin: true 656 | 657 | lilconfig@3.1.3: 658 | resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 659 | engines: {node: '>=14'} 660 | 661 | lines-and-columns@1.2.4: 662 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 663 | 664 | loose-envify@1.4.0: 665 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 666 | hasBin: true 667 | 668 | lru-cache@10.4.3: 669 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 670 | 671 | lru-cache@5.1.1: 672 | resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 673 | 674 | merge2@1.4.1: 675 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 676 | engines: {node: '>= 8'} 677 | 678 | micromatch@4.0.8: 679 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 680 | engines: {node: '>=8.6'} 681 | 682 | minimatch@9.0.5: 683 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 684 | engines: {node: '>=16 || 14 >=14.17'} 685 | 686 | minipass@7.1.2: 687 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 688 | engines: {node: '>=16 || 14 >=14.17'} 689 | 690 | ms@2.1.3: 691 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 692 | 693 | mz@2.7.0: 694 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 695 | 696 | nanoid@3.3.11: 697 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 698 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 699 | hasBin: true 700 | 701 | node-releases@2.0.19: 702 | resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 703 | 704 | normalize-path@3.0.0: 705 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 706 | engines: {node: '>=0.10.0'} 707 | 708 | normalize-range@0.1.2: 709 | resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} 710 | engines: {node: '>=0.10.0'} 711 | 712 | object-assign@4.1.1: 713 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 714 | engines: {node: '>=0.10.0'} 715 | 716 | object-hash@3.0.0: 717 | resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 718 | engines: {node: '>= 6'} 719 | 720 | package-json-from-dist@1.0.1: 721 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 722 | 723 | path-key@3.1.1: 724 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 725 | engines: {node: '>=8'} 726 | 727 | path-parse@1.0.7: 728 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 729 | 730 | path-scurry@1.11.1: 731 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 732 | engines: {node: '>=16 || 14 >=14.18'} 733 | 734 | picocolors@1.1.1: 735 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 736 | 737 | picomatch@2.3.1: 738 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 739 | engines: {node: '>=8.6'} 740 | 741 | pify@2.3.0: 742 | resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 743 | engines: {node: '>=0.10.0'} 744 | 745 | pirates@4.0.7: 746 | resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 747 | engines: {node: '>= 6'} 748 | 749 | postcss-import@15.1.0: 750 | resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 751 | engines: {node: '>=14.0.0'} 752 | peerDependencies: 753 | postcss: ^8.0.0 754 | 755 | postcss-js@4.0.1: 756 | resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 757 | engines: {node: ^12 || ^14 || >= 16} 758 | peerDependencies: 759 | postcss: ^8.4.21 760 | 761 | postcss-load-config@4.0.2: 762 | resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} 763 | engines: {node: '>= 14'} 764 | peerDependencies: 765 | postcss: '>=8.0.9' 766 | ts-node: '>=9.0.0' 767 | peerDependenciesMeta: 768 | postcss: 769 | optional: true 770 | ts-node: 771 | optional: true 772 | 773 | postcss-nested@6.2.0: 774 | resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 775 | engines: {node: '>=12.0'} 776 | peerDependencies: 777 | postcss: ^8.2.14 778 | 779 | postcss-selector-parser@6.1.2: 780 | resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 781 | engines: {node: '>=4'} 782 | 783 | postcss-value-parser@4.2.0: 784 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 785 | 786 | postcss@8.5.6: 787 | resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 788 | engines: {node: ^10 || ^12 || >=14} 789 | 790 | queue-microtask@1.2.3: 791 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 792 | 793 | react-dom@18.3.1: 794 | resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} 795 | peerDependencies: 796 | react: ^18.3.1 797 | 798 | react-refresh@0.17.0: 799 | resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} 800 | engines: {node: '>=0.10.0'} 801 | 802 | react@18.3.1: 803 | resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} 804 | engines: {node: '>=0.10.0'} 805 | 806 | read-cache@1.0.0: 807 | resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 808 | 809 | readdirp@3.6.0: 810 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 811 | engines: {node: '>=8.10.0'} 812 | 813 | resolve@1.22.10: 814 | resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} 815 | engines: {node: '>= 0.4'} 816 | hasBin: true 817 | 818 | reusify@1.1.0: 819 | resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 820 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 821 | 822 | rollup@4.44.2: 823 | resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} 824 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 825 | hasBin: true 826 | 827 | run-parallel@1.2.0: 828 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 829 | 830 | scheduler@0.23.2: 831 | resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 832 | 833 | semver@6.3.1: 834 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 835 | hasBin: true 836 | 837 | shebang-command@2.0.0: 838 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 839 | engines: {node: '>=8'} 840 | 841 | shebang-regex@3.0.0: 842 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 843 | engines: {node: '>=8'} 844 | 845 | signal-exit@4.1.0: 846 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 847 | engines: {node: '>=14'} 848 | 849 | source-map-js@1.2.1: 850 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 851 | engines: {node: '>=0.10.0'} 852 | 853 | string-width@4.2.3: 854 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 855 | engines: {node: '>=8'} 856 | 857 | string-width@5.1.2: 858 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 859 | engines: {node: '>=12'} 860 | 861 | strip-ansi@6.0.1: 862 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 863 | engines: {node: '>=8'} 864 | 865 | strip-ansi@7.1.0: 866 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 867 | engines: {node: '>=12'} 868 | 869 | sucrase@3.35.0: 870 | resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 871 | engines: {node: '>=16 || 14 >=14.17'} 872 | hasBin: true 873 | 874 | supports-preserve-symlinks-flag@1.0.0: 875 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 876 | engines: {node: '>= 0.4'} 877 | 878 | tailwindcss@3.4.17: 879 | resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} 880 | engines: {node: '>=14.0.0'} 881 | hasBin: true 882 | 883 | thenify-all@1.6.0: 884 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 885 | engines: {node: '>=0.8'} 886 | 887 | thenify@3.3.1: 888 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 889 | 890 | to-regex-range@5.0.1: 891 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 892 | engines: {node: '>=8.0'} 893 | 894 | ts-interface-checker@0.1.13: 895 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 896 | 897 | update-browserslist-db@1.1.3: 898 | resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} 899 | hasBin: true 900 | peerDependencies: 901 | browserslist: '>= 4.21.0' 902 | 903 | util-deprecate@1.0.2: 904 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 905 | 906 | vite@5.4.19: 907 | resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} 908 | engines: {node: ^18.0.0 || >=20.0.0} 909 | hasBin: true 910 | peerDependencies: 911 | '@types/node': ^18.0.0 || >=20.0.0 912 | less: '*' 913 | lightningcss: ^1.21.0 914 | sass: '*' 915 | sass-embedded: '*' 916 | stylus: '*' 917 | sugarss: '*' 918 | terser: ^5.4.0 919 | peerDependenciesMeta: 920 | '@types/node': 921 | optional: true 922 | less: 923 | optional: true 924 | lightningcss: 925 | optional: true 926 | sass: 927 | optional: true 928 | sass-embedded: 929 | optional: true 930 | stylus: 931 | optional: true 932 | sugarss: 933 | optional: true 934 | terser: 935 | optional: true 936 | 937 | which@2.0.2: 938 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 939 | engines: {node: '>= 8'} 940 | hasBin: true 941 | 942 | wrap-ansi@7.0.0: 943 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 944 | engines: {node: '>=10'} 945 | 946 | wrap-ansi@8.1.0: 947 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 948 | engines: {node: '>=12'} 949 | 950 | yallist@3.1.1: 951 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 952 | 953 | yaml@2.8.0: 954 | resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} 955 | engines: {node: '>= 14.6'} 956 | hasBin: true 957 | 958 | snapshots: 959 | 960 | '@alloc/quick-lru@5.2.0': {} 961 | 962 | '@ampproject/remapping@2.3.0': 963 | dependencies: 964 | '@jridgewell/gen-mapping': 0.3.12 965 | '@jridgewell/trace-mapping': 0.3.29 966 | 967 | '@babel/code-frame@7.27.1': 968 | dependencies: 969 | '@babel/helper-validator-identifier': 7.27.1 970 | js-tokens: 4.0.0 971 | picocolors: 1.1.1 972 | 973 | '@babel/compat-data@7.28.0': {} 974 | 975 | '@babel/core@7.28.0': 976 | dependencies: 977 | '@ampproject/remapping': 2.3.0 978 | '@babel/code-frame': 7.27.1 979 | '@babel/generator': 7.28.0 980 | '@babel/helper-compilation-targets': 7.27.2 981 | '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) 982 | '@babel/helpers': 7.27.6 983 | '@babel/parser': 7.28.0 984 | '@babel/template': 7.27.2 985 | '@babel/traverse': 7.28.0 986 | '@babel/types': 7.28.0 987 | convert-source-map: 2.0.0 988 | debug: 4.4.1 989 | gensync: 1.0.0-beta.2 990 | json5: 2.2.3 991 | semver: 6.3.1 992 | transitivePeerDependencies: 993 | - supports-color 994 | 995 | '@babel/generator@7.28.0': 996 | dependencies: 997 | '@babel/parser': 7.28.0 998 | '@babel/types': 7.28.0 999 | '@jridgewell/gen-mapping': 0.3.12 1000 | '@jridgewell/trace-mapping': 0.3.29 1001 | jsesc: 3.1.0 1002 | 1003 | '@babel/helper-compilation-targets@7.27.2': 1004 | dependencies: 1005 | '@babel/compat-data': 7.28.0 1006 | '@babel/helper-validator-option': 7.27.1 1007 | browserslist: 4.25.1 1008 | lru-cache: 5.1.1 1009 | semver: 6.3.1 1010 | 1011 | '@babel/helper-globals@7.28.0': {} 1012 | 1013 | '@babel/helper-module-imports@7.27.1': 1014 | dependencies: 1015 | '@babel/traverse': 7.28.0 1016 | '@babel/types': 7.28.0 1017 | transitivePeerDependencies: 1018 | - supports-color 1019 | 1020 | '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': 1021 | dependencies: 1022 | '@babel/core': 7.28.0 1023 | '@babel/helper-module-imports': 7.27.1 1024 | '@babel/helper-validator-identifier': 7.27.1 1025 | '@babel/traverse': 7.28.0 1026 | transitivePeerDependencies: 1027 | - supports-color 1028 | 1029 | '@babel/helper-plugin-utils@7.27.1': {} 1030 | 1031 | '@babel/helper-string-parser@7.27.1': {} 1032 | 1033 | '@babel/helper-validator-identifier@7.27.1': {} 1034 | 1035 | '@babel/helper-validator-option@7.27.1': {} 1036 | 1037 | '@babel/helpers@7.27.6': 1038 | dependencies: 1039 | '@babel/template': 7.27.2 1040 | '@babel/types': 7.28.0 1041 | 1042 | '@babel/parser@7.28.0': 1043 | dependencies: 1044 | '@babel/types': 7.28.0 1045 | 1046 | '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': 1047 | dependencies: 1048 | '@babel/core': 7.28.0 1049 | '@babel/helper-plugin-utils': 7.27.1 1050 | 1051 | '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': 1052 | dependencies: 1053 | '@babel/core': 7.28.0 1054 | '@babel/helper-plugin-utils': 7.27.1 1055 | 1056 | '@babel/template@7.27.2': 1057 | dependencies: 1058 | '@babel/code-frame': 7.27.1 1059 | '@babel/parser': 7.28.0 1060 | '@babel/types': 7.28.0 1061 | 1062 | '@babel/traverse@7.28.0': 1063 | dependencies: 1064 | '@babel/code-frame': 7.27.1 1065 | '@babel/generator': 7.28.0 1066 | '@babel/helper-globals': 7.28.0 1067 | '@babel/parser': 7.28.0 1068 | '@babel/template': 7.27.2 1069 | '@babel/types': 7.28.0 1070 | debug: 4.4.1 1071 | transitivePeerDependencies: 1072 | - supports-color 1073 | 1074 | '@babel/types@7.28.0': 1075 | dependencies: 1076 | '@babel/helper-string-parser': 7.27.1 1077 | '@babel/helper-validator-identifier': 7.27.1 1078 | 1079 | '@esbuild/aix-ppc64@0.21.5': 1080 | optional: true 1081 | 1082 | '@esbuild/android-arm64@0.21.5': 1083 | optional: true 1084 | 1085 | '@esbuild/android-arm@0.21.5': 1086 | optional: true 1087 | 1088 | '@esbuild/android-x64@0.21.5': 1089 | optional: true 1090 | 1091 | '@esbuild/darwin-arm64@0.21.5': 1092 | optional: true 1093 | 1094 | '@esbuild/darwin-x64@0.21.5': 1095 | optional: true 1096 | 1097 | '@esbuild/freebsd-arm64@0.21.5': 1098 | optional: true 1099 | 1100 | '@esbuild/freebsd-x64@0.21.5': 1101 | optional: true 1102 | 1103 | '@esbuild/linux-arm64@0.21.5': 1104 | optional: true 1105 | 1106 | '@esbuild/linux-arm@0.21.5': 1107 | optional: true 1108 | 1109 | '@esbuild/linux-ia32@0.21.5': 1110 | optional: true 1111 | 1112 | '@esbuild/linux-loong64@0.21.5': 1113 | optional: true 1114 | 1115 | '@esbuild/linux-mips64el@0.21.5': 1116 | optional: true 1117 | 1118 | '@esbuild/linux-ppc64@0.21.5': 1119 | optional: true 1120 | 1121 | '@esbuild/linux-riscv64@0.21.5': 1122 | optional: true 1123 | 1124 | '@esbuild/linux-s390x@0.21.5': 1125 | optional: true 1126 | 1127 | '@esbuild/linux-x64@0.21.5': 1128 | optional: true 1129 | 1130 | '@esbuild/netbsd-x64@0.21.5': 1131 | optional: true 1132 | 1133 | '@esbuild/openbsd-x64@0.21.5': 1134 | optional: true 1135 | 1136 | '@esbuild/sunos-x64@0.21.5': 1137 | optional: true 1138 | 1139 | '@esbuild/win32-arm64@0.21.5': 1140 | optional: true 1141 | 1142 | '@esbuild/win32-ia32@0.21.5': 1143 | optional: true 1144 | 1145 | '@esbuild/win32-x64@0.21.5': 1146 | optional: true 1147 | 1148 | '@isaacs/cliui@8.0.2': 1149 | dependencies: 1150 | string-width: 5.1.2 1151 | string-width-cjs: string-width@4.2.3 1152 | strip-ansi: 7.1.0 1153 | strip-ansi-cjs: strip-ansi@6.0.1 1154 | wrap-ansi: 8.1.0 1155 | wrap-ansi-cjs: wrap-ansi@7.0.0 1156 | 1157 | '@jridgewell/gen-mapping@0.3.12': 1158 | dependencies: 1159 | '@jridgewell/sourcemap-codec': 1.5.4 1160 | '@jridgewell/trace-mapping': 0.3.29 1161 | 1162 | '@jridgewell/resolve-uri@3.1.2': {} 1163 | 1164 | '@jridgewell/sourcemap-codec@1.5.4': {} 1165 | 1166 | '@jridgewell/trace-mapping@0.3.29': 1167 | dependencies: 1168 | '@jridgewell/resolve-uri': 3.1.2 1169 | '@jridgewell/sourcemap-codec': 1.5.4 1170 | 1171 | '@nodelib/fs.scandir@2.1.5': 1172 | dependencies: 1173 | '@nodelib/fs.stat': 2.0.5 1174 | run-parallel: 1.2.0 1175 | 1176 | '@nodelib/fs.stat@2.0.5': {} 1177 | 1178 | '@nodelib/fs.walk@1.2.8': 1179 | dependencies: 1180 | '@nodelib/fs.scandir': 2.1.5 1181 | fastq: 1.19.1 1182 | 1183 | '@pkgjs/parseargs@0.11.0': 1184 | optional: true 1185 | 1186 | '@rolldown/pluginutils@1.0.0-beta.19': {} 1187 | 1188 | '@rollup/rollup-android-arm-eabi@4.44.2': 1189 | optional: true 1190 | 1191 | '@rollup/rollup-android-arm64@4.44.2': 1192 | optional: true 1193 | 1194 | '@rollup/rollup-darwin-arm64@4.44.2': 1195 | optional: true 1196 | 1197 | '@rollup/rollup-darwin-x64@4.44.2': 1198 | optional: true 1199 | 1200 | '@rollup/rollup-freebsd-arm64@4.44.2': 1201 | optional: true 1202 | 1203 | '@rollup/rollup-freebsd-x64@4.44.2': 1204 | optional: true 1205 | 1206 | '@rollup/rollup-linux-arm-gnueabihf@4.44.2': 1207 | optional: true 1208 | 1209 | '@rollup/rollup-linux-arm-musleabihf@4.44.2': 1210 | optional: true 1211 | 1212 | '@rollup/rollup-linux-arm64-gnu@4.44.2': 1213 | optional: true 1214 | 1215 | '@rollup/rollup-linux-arm64-musl@4.44.2': 1216 | optional: true 1217 | 1218 | '@rollup/rollup-linux-loongarch64-gnu@4.44.2': 1219 | optional: true 1220 | 1221 | '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': 1222 | optional: true 1223 | 1224 | '@rollup/rollup-linux-riscv64-gnu@4.44.2': 1225 | optional: true 1226 | 1227 | '@rollup/rollup-linux-riscv64-musl@4.44.2': 1228 | optional: true 1229 | 1230 | '@rollup/rollup-linux-s390x-gnu@4.44.2': 1231 | optional: true 1232 | 1233 | '@rollup/rollup-linux-x64-gnu@4.44.2': 1234 | optional: true 1235 | 1236 | '@rollup/rollup-linux-x64-musl@4.44.2': 1237 | optional: true 1238 | 1239 | '@rollup/rollup-win32-arm64-msvc@4.44.2': 1240 | optional: true 1241 | 1242 | '@rollup/rollup-win32-ia32-msvc@4.44.2': 1243 | optional: true 1244 | 1245 | '@rollup/rollup-win32-x64-msvc@4.44.2': 1246 | optional: true 1247 | 1248 | '@types/babel__core@7.20.5': 1249 | dependencies: 1250 | '@babel/parser': 7.28.0 1251 | '@babel/types': 7.28.0 1252 | '@types/babel__generator': 7.27.0 1253 | '@types/babel__template': 7.4.4 1254 | '@types/babel__traverse': 7.20.7 1255 | 1256 | '@types/babel__generator@7.27.0': 1257 | dependencies: 1258 | '@babel/types': 7.28.0 1259 | 1260 | '@types/babel__template@7.4.4': 1261 | dependencies: 1262 | '@babel/parser': 7.28.0 1263 | '@babel/types': 7.28.0 1264 | 1265 | '@types/babel__traverse@7.20.7': 1266 | dependencies: 1267 | '@babel/types': 7.28.0 1268 | 1269 | '@types/estree@1.0.8': {} 1270 | 1271 | '@types/prop-types@15.7.15': {} 1272 | 1273 | '@types/react-dom@18.3.7(@types/react@18.3.23)': 1274 | dependencies: 1275 | '@types/react': 18.3.23 1276 | 1277 | '@types/react@18.3.23': 1278 | dependencies: 1279 | '@types/prop-types': 15.7.15 1280 | csstype: 3.1.3 1281 | 1282 | '@vitejs/plugin-react@4.6.0(vite@5.4.19)': 1283 | dependencies: 1284 | '@babel/core': 7.28.0 1285 | '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) 1286 | '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) 1287 | '@rolldown/pluginutils': 1.0.0-beta.19 1288 | '@types/babel__core': 7.20.5 1289 | react-refresh: 0.17.0 1290 | vite: 5.4.19 1291 | transitivePeerDependencies: 1292 | - supports-color 1293 | 1294 | ansi-regex@5.0.1: {} 1295 | 1296 | ansi-regex@6.1.0: {} 1297 | 1298 | ansi-styles@4.3.0: 1299 | dependencies: 1300 | color-convert: 2.0.1 1301 | 1302 | ansi-styles@6.2.1: {} 1303 | 1304 | any-promise@1.3.0: {} 1305 | 1306 | anymatch@3.1.3: 1307 | dependencies: 1308 | normalize-path: 3.0.0 1309 | picomatch: 2.3.1 1310 | 1311 | arg@5.0.2: {} 1312 | 1313 | autoprefixer@10.4.21(postcss@8.5.6): 1314 | dependencies: 1315 | browserslist: 4.25.1 1316 | caniuse-lite: 1.0.30001726 1317 | fraction.js: 4.3.7 1318 | normalize-range: 0.1.2 1319 | picocolors: 1.1.1 1320 | postcss: 8.5.6 1321 | postcss-value-parser: 4.2.0 1322 | 1323 | balanced-match@1.0.2: {} 1324 | 1325 | binary-extensions@2.3.0: {} 1326 | 1327 | brace-expansion@2.0.2: 1328 | dependencies: 1329 | balanced-match: 1.0.2 1330 | 1331 | braces@3.0.3: 1332 | dependencies: 1333 | fill-range: 7.1.1 1334 | 1335 | browserslist@4.25.1: 1336 | dependencies: 1337 | caniuse-lite: 1.0.30001726 1338 | electron-to-chromium: 1.5.179 1339 | node-releases: 2.0.19 1340 | update-browserslist-db: 1.1.3(browserslist@4.25.1) 1341 | 1342 | camelcase-css@2.0.1: {} 1343 | 1344 | caniuse-lite@1.0.30001726: {} 1345 | 1346 | chokidar@3.6.0: 1347 | dependencies: 1348 | anymatch: 3.1.3 1349 | braces: 3.0.3 1350 | glob-parent: 5.1.2 1351 | is-binary-path: 2.1.0 1352 | is-glob: 4.0.3 1353 | normalize-path: 3.0.0 1354 | readdirp: 3.6.0 1355 | optionalDependencies: 1356 | fsevents: 2.3.3 1357 | 1358 | color-convert@2.0.1: 1359 | dependencies: 1360 | color-name: 1.1.4 1361 | 1362 | color-name@1.1.4: {} 1363 | 1364 | commander@4.1.1: {} 1365 | 1366 | convert-source-map@2.0.0: {} 1367 | 1368 | cross-spawn@7.0.6: 1369 | dependencies: 1370 | path-key: 3.1.1 1371 | shebang-command: 2.0.0 1372 | which: 2.0.2 1373 | 1374 | cssesc@3.0.0: {} 1375 | 1376 | csstype@3.1.3: {} 1377 | 1378 | debug@4.4.1: 1379 | dependencies: 1380 | ms: 2.1.3 1381 | 1382 | didyoumean@1.2.2: {} 1383 | 1384 | dlv@1.1.3: {} 1385 | 1386 | eastasianwidth@0.2.0: {} 1387 | 1388 | electron-to-chromium@1.5.179: {} 1389 | 1390 | emoji-regex@8.0.0: {} 1391 | 1392 | emoji-regex@9.2.2: {} 1393 | 1394 | esbuild@0.21.5: 1395 | optionalDependencies: 1396 | '@esbuild/aix-ppc64': 0.21.5 1397 | '@esbuild/android-arm': 0.21.5 1398 | '@esbuild/android-arm64': 0.21.5 1399 | '@esbuild/android-x64': 0.21.5 1400 | '@esbuild/darwin-arm64': 0.21.5 1401 | '@esbuild/darwin-x64': 0.21.5 1402 | '@esbuild/freebsd-arm64': 0.21.5 1403 | '@esbuild/freebsd-x64': 0.21.5 1404 | '@esbuild/linux-arm': 0.21.5 1405 | '@esbuild/linux-arm64': 0.21.5 1406 | '@esbuild/linux-ia32': 0.21.5 1407 | '@esbuild/linux-loong64': 0.21.5 1408 | '@esbuild/linux-mips64el': 0.21.5 1409 | '@esbuild/linux-ppc64': 0.21.5 1410 | '@esbuild/linux-riscv64': 0.21.5 1411 | '@esbuild/linux-s390x': 0.21.5 1412 | '@esbuild/linux-x64': 0.21.5 1413 | '@esbuild/netbsd-x64': 0.21.5 1414 | '@esbuild/openbsd-x64': 0.21.5 1415 | '@esbuild/sunos-x64': 0.21.5 1416 | '@esbuild/win32-arm64': 0.21.5 1417 | '@esbuild/win32-ia32': 0.21.5 1418 | '@esbuild/win32-x64': 0.21.5 1419 | 1420 | escalade@3.2.0: {} 1421 | 1422 | fast-glob@3.3.3: 1423 | dependencies: 1424 | '@nodelib/fs.stat': 2.0.5 1425 | '@nodelib/fs.walk': 1.2.8 1426 | glob-parent: 5.1.2 1427 | merge2: 1.4.1 1428 | micromatch: 4.0.8 1429 | 1430 | fastq@1.19.1: 1431 | dependencies: 1432 | reusify: 1.1.0 1433 | 1434 | fill-range@7.1.1: 1435 | dependencies: 1436 | to-regex-range: 5.0.1 1437 | 1438 | foreground-child@3.3.1: 1439 | dependencies: 1440 | cross-spawn: 7.0.6 1441 | signal-exit: 4.1.0 1442 | 1443 | fraction.js@4.3.7: {} 1444 | 1445 | fsevents@2.3.3: 1446 | optional: true 1447 | 1448 | function-bind@1.1.2: {} 1449 | 1450 | gensync@1.0.0-beta.2: {} 1451 | 1452 | glob-parent@5.1.2: 1453 | dependencies: 1454 | is-glob: 4.0.3 1455 | 1456 | glob-parent@6.0.2: 1457 | dependencies: 1458 | is-glob: 4.0.3 1459 | 1460 | glob@10.4.5: 1461 | dependencies: 1462 | foreground-child: 3.3.1 1463 | jackspeak: 3.4.3 1464 | minimatch: 9.0.5 1465 | minipass: 7.1.2 1466 | package-json-from-dist: 1.0.1 1467 | path-scurry: 1.11.1 1468 | 1469 | hasown@2.0.2: 1470 | dependencies: 1471 | function-bind: 1.1.2 1472 | 1473 | is-binary-path@2.1.0: 1474 | dependencies: 1475 | binary-extensions: 2.3.0 1476 | 1477 | is-core-module@2.16.1: 1478 | dependencies: 1479 | hasown: 2.0.2 1480 | 1481 | is-extglob@2.1.1: {} 1482 | 1483 | is-fullwidth-code-point@3.0.0: {} 1484 | 1485 | is-glob@4.0.3: 1486 | dependencies: 1487 | is-extglob: 2.1.1 1488 | 1489 | is-number@7.0.0: {} 1490 | 1491 | isexe@2.0.0: {} 1492 | 1493 | jackspeak@3.4.3: 1494 | dependencies: 1495 | '@isaacs/cliui': 8.0.2 1496 | optionalDependencies: 1497 | '@pkgjs/parseargs': 0.11.0 1498 | 1499 | jiti@1.21.7: {} 1500 | 1501 | js-tokens@4.0.0: {} 1502 | 1503 | jsesc@3.1.0: {} 1504 | 1505 | json5@2.2.3: {} 1506 | 1507 | lilconfig@3.1.3: {} 1508 | 1509 | lines-and-columns@1.2.4: {} 1510 | 1511 | loose-envify@1.4.0: 1512 | dependencies: 1513 | js-tokens: 4.0.0 1514 | 1515 | lru-cache@10.4.3: {} 1516 | 1517 | lru-cache@5.1.1: 1518 | dependencies: 1519 | yallist: 3.1.1 1520 | 1521 | merge2@1.4.1: {} 1522 | 1523 | micromatch@4.0.8: 1524 | dependencies: 1525 | braces: 3.0.3 1526 | picomatch: 2.3.1 1527 | 1528 | minimatch@9.0.5: 1529 | dependencies: 1530 | brace-expansion: 2.0.2 1531 | 1532 | minipass@7.1.2: {} 1533 | 1534 | ms@2.1.3: {} 1535 | 1536 | mz@2.7.0: 1537 | dependencies: 1538 | any-promise: 1.3.0 1539 | object-assign: 4.1.1 1540 | thenify-all: 1.6.0 1541 | 1542 | nanoid@3.3.11: {} 1543 | 1544 | node-releases@2.0.19: {} 1545 | 1546 | normalize-path@3.0.0: {} 1547 | 1548 | normalize-range@0.1.2: {} 1549 | 1550 | object-assign@4.1.1: {} 1551 | 1552 | object-hash@3.0.0: {} 1553 | 1554 | package-json-from-dist@1.0.1: {} 1555 | 1556 | path-key@3.1.1: {} 1557 | 1558 | path-parse@1.0.7: {} 1559 | 1560 | path-scurry@1.11.1: 1561 | dependencies: 1562 | lru-cache: 10.4.3 1563 | minipass: 7.1.2 1564 | 1565 | picocolors@1.1.1: {} 1566 | 1567 | picomatch@2.3.1: {} 1568 | 1569 | pify@2.3.0: {} 1570 | 1571 | pirates@4.0.7: {} 1572 | 1573 | postcss-import@15.1.0(postcss@8.5.6): 1574 | dependencies: 1575 | postcss: 8.5.6 1576 | postcss-value-parser: 4.2.0 1577 | read-cache: 1.0.0 1578 | resolve: 1.22.10 1579 | 1580 | postcss-js@4.0.1(postcss@8.5.6): 1581 | dependencies: 1582 | camelcase-css: 2.0.1 1583 | postcss: 8.5.6 1584 | 1585 | postcss-load-config@4.0.2(postcss@8.5.6): 1586 | dependencies: 1587 | lilconfig: 3.1.3 1588 | yaml: 2.8.0 1589 | optionalDependencies: 1590 | postcss: 8.5.6 1591 | 1592 | postcss-nested@6.2.0(postcss@8.5.6): 1593 | dependencies: 1594 | postcss: 8.5.6 1595 | postcss-selector-parser: 6.1.2 1596 | 1597 | postcss-selector-parser@6.1.2: 1598 | dependencies: 1599 | cssesc: 3.0.0 1600 | util-deprecate: 1.0.2 1601 | 1602 | postcss-value-parser@4.2.0: {} 1603 | 1604 | postcss@8.5.6: 1605 | dependencies: 1606 | nanoid: 3.3.11 1607 | picocolors: 1.1.1 1608 | source-map-js: 1.2.1 1609 | 1610 | queue-microtask@1.2.3: {} 1611 | 1612 | react-dom@18.3.1(react@18.3.1): 1613 | dependencies: 1614 | loose-envify: 1.4.0 1615 | react: 18.3.1 1616 | scheduler: 0.23.2 1617 | 1618 | react-refresh@0.17.0: {} 1619 | 1620 | react@18.3.1: 1621 | dependencies: 1622 | loose-envify: 1.4.0 1623 | 1624 | read-cache@1.0.0: 1625 | dependencies: 1626 | pify: 2.3.0 1627 | 1628 | readdirp@3.6.0: 1629 | dependencies: 1630 | picomatch: 2.3.1 1631 | 1632 | resolve@1.22.10: 1633 | dependencies: 1634 | is-core-module: 2.16.1 1635 | path-parse: 1.0.7 1636 | supports-preserve-symlinks-flag: 1.0.0 1637 | 1638 | reusify@1.1.0: {} 1639 | 1640 | rollup@4.44.2: 1641 | dependencies: 1642 | '@types/estree': 1.0.8 1643 | optionalDependencies: 1644 | '@rollup/rollup-android-arm-eabi': 4.44.2 1645 | '@rollup/rollup-android-arm64': 4.44.2 1646 | '@rollup/rollup-darwin-arm64': 4.44.2 1647 | '@rollup/rollup-darwin-x64': 4.44.2 1648 | '@rollup/rollup-freebsd-arm64': 4.44.2 1649 | '@rollup/rollup-freebsd-x64': 4.44.2 1650 | '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 1651 | '@rollup/rollup-linux-arm-musleabihf': 4.44.2 1652 | '@rollup/rollup-linux-arm64-gnu': 4.44.2 1653 | '@rollup/rollup-linux-arm64-musl': 4.44.2 1654 | '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 1655 | '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 1656 | '@rollup/rollup-linux-riscv64-gnu': 4.44.2 1657 | '@rollup/rollup-linux-riscv64-musl': 4.44.2 1658 | '@rollup/rollup-linux-s390x-gnu': 4.44.2 1659 | '@rollup/rollup-linux-x64-gnu': 4.44.2 1660 | '@rollup/rollup-linux-x64-musl': 4.44.2 1661 | '@rollup/rollup-win32-arm64-msvc': 4.44.2 1662 | '@rollup/rollup-win32-ia32-msvc': 4.44.2 1663 | '@rollup/rollup-win32-x64-msvc': 4.44.2 1664 | fsevents: 2.3.3 1665 | 1666 | run-parallel@1.2.0: 1667 | dependencies: 1668 | queue-microtask: 1.2.3 1669 | 1670 | scheduler@0.23.2: 1671 | dependencies: 1672 | loose-envify: 1.4.0 1673 | 1674 | semver@6.3.1: {} 1675 | 1676 | shebang-command@2.0.0: 1677 | dependencies: 1678 | shebang-regex: 3.0.0 1679 | 1680 | shebang-regex@3.0.0: {} 1681 | 1682 | signal-exit@4.1.0: {} 1683 | 1684 | source-map-js@1.2.1: {} 1685 | 1686 | string-width@4.2.3: 1687 | dependencies: 1688 | emoji-regex: 8.0.0 1689 | is-fullwidth-code-point: 3.0.0 1690 | strip-ansi: 6.0.1 1691 | 1692 | string-width@5.1.2: 1693 | dependencies: 1694 | eastasianwidth: 0.2.0 1695 | emoji-regex: 9.2.2 1696 | strip-ansi: 7.1.0 1697 | 1698 | strip-ansi@6.0.1: 1699 | dependencies: 1700 | ansi-regex: 5.0.1 1701 | 1702 | strip-ansi@7.1.0: 1703 | dependencies: 1704 | ansi-regex: 6.1.0 1705 | 1706 | sucrase@3.35.0: 1707 | dependencies: 1708 | '@jridgewell/gen-mapping': 0.3.12 1709 | commander: 4.1.1 1710 | glob: 10.4.5 1711 | lines-and-columns: 1.2.4 1712 | mz: 2.7.0 1713 | pirates: 4.0.7 1714 | ts-interface-checker: 0.1.13 1715 | 1716 | supports-preserve-symlinks-flag@1.0.0: {} 1717 | 1718 | tailwindcss@3.4.17: 1719 | dependencies: 1720 | '@alloc/quick-lru': 5.2.0 1721 | arg: 5.0.2 1722 | chokidar: 3.6.0 1723 | didyoumean: 1.2.2 1724 | dlv: 1.1.3 1725 | fast-glob: 3.3.3 1726 | glob-parent: 6.0.2 1727 | is-glob: 4.0.3 1728 | jiti: 1.21.7 1729 | lilconfig: 3.1.3 1730 | micromatch: 4.0.8 1731 | normalize-path: 3.0.0 1732 | object-hash: 3.0.0 1733 | picocolors: 1.1.1 1734 | postcss: 8.5.6 1735 | postcss-import: 15.1.0(postcss@8.5.6) 1736 | postcss-js: 4.0.1(postcss@8.5.6) 1737 | postcss-load-config: 4.0.2(postcss@8.5.6) 1738 | postcss-nested: 6.2.0(postcss@8.5.6) 1739 | postcss-selector-parser: 6.1.2 1740 | resolve: 1.22.10 1741 | sucrase: 3.35.0 1742 | transitivePeerDependencies: 1743 | - ts-node 1744 | 1745 | thenify-all@1.6.0: 1746 | dependencies: 1747 | thenify: 3.3.1 1748 | 1749 | thenify@3.3.1: 1750 | dependencies: 1751 | any-promise: 1.3.0 1752 | 1753 | to-regex-range@5.0.1: 1754 | dependencies: 1755 | is-number: 7.0.0 1756 | 1757 | ts-interface-checker@0.1.13: {} 1758 | 1759 | update-browserslist-db@1.1.3(browserslist@4.25.1): 1760 | dependencies: 1761 | browserslist: 4.25.1 1762 | escalade: 3.2.0 1763 | picocolors: 1.1.1 1764 | 1765 | util-deprecate@1.0.2: {} 1766 | 1767 | vite@5.4.19: 1768 | dependencies: 1769 | esbuild: 0.21.5 1770 | postcss: 8.5.6 1771 | rollup: 4.44.2 1772 | optionalDependencies: 1773 | fsevents: 2.3.3 1774 | 1775 | which@2.0.2: 1776 | dependencies: 1777 | isexe: 2.0.0 1778 | 1779 | wrap-ansi@7.0.0: 1780 | dependencies: 1781 | ansi-styles: 4.3.0 1782 | string-width: 4.2.3 1783 | strip-ansi: 6.0.1 1784 | 1785 | wrap-ansi@8.1.0: 1786 | dependencies: 1787 | ansi-styles: 6.2.1 1788 | string-width: 5.1.2 1789 | strip-ansi: 7.1.0 1790 | 1791 | yallist@3.1.1: {} 1792 | 1793 | yaml@2.8.0: {} 1794 | --------------------------------------------------------------------------------