.github/000077500000000000000000000000001517716222300124245ustar00rootroot00000000000000.github/workflows/000077500000000000000000000000001517716222300144615ustar00rootroot00000000000000.github/workflows/build.yml000066400000000000000000000017151517716222300163070ustar00rootroot00000000000000# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: build on: schedule: - cron: '0 0 1 * *' push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: ['lts/*'] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - run: npm i -g grunt-cli - run: npm install --legacy-peer-deps - run: grunt .gitignore000066400000000000000000000003731517716222300130570ustar00rootroot00000000000000/build /node_modules /*.log /*.iws .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity /p3xrs.json .DS_Store /dist.npmignore000066400000000000000000000003411517716222300130610ustar00rootroot00000000000000/.idea /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json /secure /.github /.vscode /src/**/*.* secure/ agents/ .claude/ .codex/ CLAUDE.md AGENTS.md Gruntfile.cjs000066400000000000000000000004121517716222300135210ustar00rootroot00000000000000 module.exports = (grunt) => { const _ = require('lodash'); const builder = require(`corifeus-builder`); const loader = new builder.loader(grunt); loader.js({ }); grunt.registerTask('default', ['cory-npm', 'clean', 'cory-replace']); } LICENSE000066400000000000000000000021261517716222300120720ustar00rootroot00000000000000P3X Redis UI License Copyright (c) Patrik Laszlo / PatrikX3 / Corifeus MIT LICENSE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000234341517716222300123510ustar00rootroot00000000000000# This is a development package For the full-blown package, please follow: https://github.com/patrikx3/redis-ui https://www.npmjs.com/package/p3x-redis-ui https://corifeus.com/redis-ui [//]: #@corifeus-header [![NPM](https://img.shields.io/npm/v/p3x-redis-ui-server.svg)](https://www.npmjs.com/package/p3x-redis-ui-server) [![Donate for PatrikX3 / P3X](https://img.shields.io/badge/Donate-PatrikX3-003087.svg)](https://paypal.me/patrikx3) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Corifeus @ Facebook](https://img.shields.io/badge/Facebook-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Uptime ratio (90 days)](https://network.corifeus.com/public/api/uptime-shield/31ad7a5c194347c33e5445dbaf8.svg)](https://network.corifeus.com/status/31ad7a5c194347c33e5445dbaf8) --- # 🏍️ P3X Redis UI server β€” Socket.IO backend for the dual Angular + React frontend with AI queries, 54 languages, and auto data decompression v2026.4.373 🌌 **Bugs are evidentβ„’ - MATRIX️** 🚧 **This project is under active development!** πŸ“’ **We welcome your feedback and contributions.** ### NodeJS LTS is supported ### πŸ› οΈ Built on NodeJs version ```txt v24.14.1 ``` # πŸ“ Description [//]: #@corifeus-header:end This version requires minimum Node.js v22. This is part of the composable `p3x-redis-ui` package β€” the backend server built entirely on Socket.IO (no REST). The server connects to the `p3x-redis-ui-material` dual frontend: - **Angular** frontend (`/ng/`) β€” Angular + Angular Material + Webpack - **React** frontend (`/react/`) β€” React + MUI + Vite + Zustand Both frontends share the same Socket.IO protocol, **54 languages**, **7 themes** (4 dark + 3 light with auto system preference), and all features at full parity. Users can switch between Angular and React live in Settings. ### Key Capabilities - **Socket.IO real-time communication** β€” all Redis operations via WebSocket events - **ioredis client** β€” standalone, cluster, and sentinel support with optional SSH tunneling - **Built-in login page** β€” custom JWT-based authentication with themed login dialog (replaces browser HTTP Basic Auth) - **AI query translation** β€” natural language to Redis commands via Groq API, with bash pipe to EVAL Lua conversion - **Auto data decompression** β€” GZIP, ZIP, zlib, Zstandard, LZ4, Snappy, Brotli - **Desktop notifications** β€” Electron native + Web Notification API for disconnect/reconnect events - **Auto language detection** β€” matches browser/system locale to one of 54 supported languages - **Health check endpoint** β€” `GET /health` for Docker/Kubernetes probes - **Graceful shutdown** β€” handles SIGTERM/SIGINT, closes all connections cleanly ## Configuration For now, there are 2 configuration files: ```bash p3xrs --config ./p3xrs.json ``` The 2nd configuration is the list of the connections if found in `p3xrs.json` it either in the config: ```text p3xrs.json/p3xrs.connections['home-dir'] = undefined|home|absolute|relative ``` The best is to keep it undefined and it will be in your home dir, but you can choose any place as well. You may also set connections file name which overrides default .p3xrs-conns.json ```text p3xrs --connections-file-name .p3xrs-conns.json ``` ## Authentication (Login Page) Instead of relying on the browser's native HTTP Basic Auth dialog, P3X Redis UI has its own built-in login page. When authentication is enabled, the app displays a themed login dialog that integrates seamlessly with the UI -- supporting all 54 languages, all 7 themes, and the Angular/React GUI switcher. The login flow uses JWT tokens (HS256, 24-hour expiry) with no external dependencies. The token is stored in the browser's localStorage and sent via Socket.IO handshake. A logout button appears in the top-right corner of the header toolbar when auth is active. Config (`p3xrs.json`): ```json { "p3xrs": { "httpAuth": { "enabled": true, "username": "admin", "passwordHash": "$2b$10$..." } } } ``` Generate BCrypt password hash: ```bash node ./bin/bcrypt-password.mjs -p myplainpass ``` Environment variables: - `HTTP_USER` - `HTTP_PASSWORD` - `HTTP_PASSWORD_HASH` - `HTTP_PASSWORD_HASH_FILE` - `HTTP_AUTH_ENABLED` (`true|false`) CLI options: - `--http-auth-enable` - `--http-auth-disable` - `--http-auth-username` - `--http-auth-password` - `--http-auth-password-hash` - `--http-auth-password-hash-file` Notes: - `passwordHash` is preferred over plain `password`. - Use HTTPS/reverse proxy TLS when auth is enabled. - JWT tokens expire on server restart (secret is derived from the password hash). ## AI-Powered Redis Query Translation The AI feature translates natural language or CLI-style input into valid Redis commands. It supports bash-style pipe operations (e.g. `keys session:* | head -20 | sort`) by converting them into Redis EVAL Lua scripts that run atomically on the server. - Powered by Groq API (model: `openai/gpt-oss-120b`) - Supports all human languages as input - Bash pipes (`head`, `tail`, `grep`, `sort`, `wc`, `uniq`) are translated to EVAL Lua - Network proxy mode (via `network.corifeus.com`) or direct API key mode - Configure via `--groq-api-key` CLI flag or the AI Settings panel in the UI ### Verbose CLI help ```text Usage: p3xrs [options] Options: -V, --version output the version number -c, --config [config] Set the p3xr.json p3x-redis-ui-server configuration, see more help in p3x-redis-ui-server -r, --readonly-connections Set the connections to be readonly, no adding, saving or delete a connection -n, --connections-file-name [filename] Set the connections file name, overrides default .p3xrs-conns.json --http-auth-enable Enable HTTP Basic auth --http-auth-disable Disable HTTP Basic auth --http-auth-username [username] HTTP Basic auth username --http-auth-password [password] HTTP Basic auth plain password --http-auth-password-hash [hash] HTTP Basic auth bcrypt password hash --http-auth-password-hash-file [file] Read HTTP Basic auth bcrypt password hash from file --groq-api-key [key] Groq API key for AI-powered Redis query translation (get a free key at console.groq.com) --groq-api-key-readonly Prevent users from changing the Groq API key via the UI -h, --help display help for command ``` # For development standalone For file names do not use camelCase, but use kebab-case. Folder should be named as kebab-case as well. As you can see, all code filenames are using it like that, please do not change that. Please apply the `.editorconfig` settings in your IDE. Copy from `./artifacts/boot/p3xrs.json` to the root folder (`./p3xrs.json`). ```bash yarn install yarn run dev ``` It uses `nodemon` and when any file is changed, it will re-load it. The server app is available @ http://localhost:7843 [//]: #@corifeus-footer --- # Corifeus Network AI-powered network & email toolkit β€” free, no signup. **Web** Β· [network.corifeus.com](https://network.corifeus.com) **MCP** Β· [`npm i -g p3x-network-mcp`](https://www.npmjs.com/package/p3x-network-mcp) - **AI Network Assistant** β€” ask in plain language, get a full domain health report - **Network Audit** β€” DNS, SSL, security headers, DNSBL, BGP, IPv6, geolocation in one call - **Diagnostics** β€” DNS lookup & global propagation, WHOIS, reverse DNS, HTTP check, my-IP - **Mail Tester** β€” live SPF/DKIM/DMARC + spam score + AI fix suggestions, results emailed (localized) - **Monitoring** β€” TCP / HTTP / Ping with alerts and public status pages - **MCP server** β€” 17 tools exposed to Claude Code, Codex, Cursor, any MCP client - **Install** β€” `claude mcp add p3x-network -- npx p3x-network-mcp` - **Try** β€” *"audit example.com"*, *"why do my emails land in spam? test me@example.com"* - **Source** β€” [patrikx3/network](https://github.com/patrikx3/network) Β· [patrikx3/network-mcp](https://github.com/patrikx3/network-mcp) - **Contact** β€” [patrikx3.com](https://www.patrikx3.com/en/front/contact) Β· [donate](https://paypal.me/patrikx3) --- ## ❀️ Support Our Open-Source Project If you appreciate our work, consider ⭐ starring this repository or πŸ’° making a donation to support server maintenance and ongoing development. Your support means the world to usβ€”thank you! --- ### 🌍 About My Domains All my domains, including [patrikx3.com](https://patrikx3.com), [corifeus.eu](https://corifeus.eu), and [corifeus.com](https://corifeus.com), are developed in my spare time. While you may encounter minor errors, the sites are generally stable and fully functional. --- ### πŸ“ˆ Versioning Policy **Version Structure:** We follow a **Major.Minor.Patch** versioning scheme: - **Major:** πŸ“… Corresponds to the current year. - **Minor:** πŸŒ“ Set as 4 for releases from January to June, and 10 for July to December. - **Patch:** πŸ”§ Incremental, updated with each build. **🚨 Important Changes:** Any breaking changes are prominently noted in the readme to keep you informed. [**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.373 [![NPM](https://img.shields.io/npm/v/p3x-redis-ui-server.svg)](https://www.npmjs.com/package/p3x-redis-ui-server) [![Donate for PatrikX3 / P3X](https://img.shields.io/badge/Donate-PatrikX3-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001517716222300130445ustar00rootroot00000000000000artifacts/boot/000077500000000000000000000000001517716222300140075ustar00rootroot00000000000000artifacts/boot/p3xrs.json000066400000000000000000000024651517716222300157700ustar00rootroot00000000000000{ "p3xrs": { "http": { "port-info": "this is ommitted, it will be default 7843", "port": 7843, "bind-info": "the interface with listen to, could be 127.0.0.1 or 0.0.0.0 or specific interface", "bind": "0.0.0.0" }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static-disabled": "~p3x-redis-ui-material/dist", "httpAuth-info": "Optional HTTP Basic auth for HTTP + Socket.IO. Prefer passwordHash (bcrypt).", "httpAuth": { "enabled": false, "username": "admin", "password": "", "passwordHash": "" }, "licenseEditable": true, "licenseKey": "", "treeDividers": [ ":", "/", "|", "-", "@" ] } } bin/000077500000000000000000000000001517716222300116345ustar00rootroot00000000000000bin/bcrypt-password.mjs000066400000000000000000000010701517716222300155100ustar00rootroot00000000000000#!/usr/bin/env node import { program } from 'commander' import bcrypt from 'bcryptjs' program .requiredOption('-p, --password ', 'Password to hash') .option('-r, --rounds ', 'BCrypt rounds', '10') .parse(process.argv) const options = program.opts() const rounds = Number.parseInt(options.rounds, 10) if (!Number.isInteger(rounds) || rounds < 4 || rounds > 31) { console.error('Invalid rounds value. Use an integer between 4 and 31.') process.exit(1) } const hash = bcrypt.hashSync(options.password, rounds) console.log(hash) bin/p3xrs.mjs000066400000000000000000000011551517716222300134300ustar00rootroot00000000000000#!/usr/bin/env node import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isDevelopment = process.env.NODE_ENV === 'development' const srcBootModule = '../src/lib/boot.mjs' const distBootModule = '../dist/lib/boot.mjs' const distBootFilename = path.resolve(__dirname, '../dist/lib/boot.mjs') const bootModule = isDevelopment || !fs.existsSync(distBootFilename) ? srcBootModule : distBootModule const bootPath = path.resolve(__dirname, bootModule) const mod = await import(bootPath) const boot = mod.default boot() package.json000066400000000000000000000047141517716222300133600ustar00rootroot00000000000000{ "name": "p3x-redis-ui-server", "version": "2026.4.373", "description": "🏍️ P3X Redis UI server β€” Socket.IO backend for the dual Angular + React frontend with AI queries, 54 languages, and auto data decompression", "corifeus": { "icon": "fas fa-flag-checkered", "code": "Reverse", "opencollective": false, "build": true, "nodejs": "v24.14.1", "reponame": "redis-ui-server", "publish": true, "prefix": "p3x-", "type": "p3x" }, "type": "module", "main": "dist/index.mjs", "bin": { "p3xrs": "./bin/p3xrs.mjs" }, "scripts": { "test": "grunt --gruntfile Gruntfile.cjs", "build:compressed": "node ./scripts/build-compressed.mjs", "start": "node ./bin/p3xrs.mjs --config ./p3xrs.json", "dev": "NODE_ENV=development nodemon --watch src --watch package.json --watch bin ./bin/p3xrs.mjs --config ../p3xrs.json", "dev-readonly-connections": "NODE_ENV=development nodemon --watch src --watch package.json --watch bin ./bin/p3xrs.mjs --config ../p3xrs.json --readonly-connections", "dev-auth": "NODE_ENV=development nodemon --watch src --watch package.json --watch bin ./bin/p3xrs.mjs --config ../p3xrs.json --http-auth-enable --http-auth-username admin --http-auth-password admin" }, "watch": { "run": "src/**/*.mjs" }, "repository": { "type": "git", "url": "https://github.com/patrikx3/redis-ui-server.git" }, "keywords": [ "redis", "ui", "gui", "web", "electron", "desktop", "server", "angularjs", "javascript", "material", "dark", "light" ], "author": "Patrik Laszlo ", "license": "MIT", "devDependencies": { "corifeus-builder": "^2026.4.154", "nodemon": "^3.1.14", "terser": "^5.46.2" }, "dependencies": { "@msgpack/msgpack": "^3.1.3", "bcryptjs": "^3.0.3", "chalk": "^5.6.2", "commander": "^14.0.3", "corifeus-utils": "^2026.4.135", "express": "^5.2.1", "groq-sdk": "^1.1.2", "ioredis": "^5.10.1", "lodash-es": "^4.18.1", "lz4js": "^0.2.0", "snappyjs": "^0.7.0", "socket.io": "^4.8.3", "tunnel-ssh": "^5.2.0" }, "engines": { "node": ">=12.13.0" }, "homepage": "https://corifeus.com/redis-ui-server" }scripts/000077500000000000000000000000001517716222300125535ustar00rootroot00000000000000scripts/build-compressed.mjs000066400000000000000000000045271517716222300165370ustar00rootroot00000000000000#!/usr/bin/env node import fs from 'fs/promises' import path from 'path' import { fileURLToPath } from 'url' import { minify } from 'terser' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootDir = path.resolve(__dirname, '..') const srcDir = path.join(rootDir, 'src') const distDir = path.join(rootDir, 'dist') const walkFiles = async (dir) => { const entries = await fs.readdir(dir, { withFileTypes: true }) const files = [] for (const entry of entries) { const fullPath = path.join(dir, entry.name) if (entry.isDirectory()) { files.push(...await walkFiles(fullPath)) } else if (entry.isFile()) { files.push(fullPath) } } return files } const minifyJavaScript = async (sourceCode, relativePath) => { const result = await minify(sourceCode, { module: true, compress: { passes: 2, keep_infinity: true, unsafe_arrows: true, }, mangle: true, format: { ascii_only: true, comments: false, }, }) if (!result || typeof result.code !== 'string' || result.code.length === 0) { throw new Error(`minify-empty-output: ${relativePath}`) } return `${result.code}\n` } const buildCompressed = async () => { console.log('build-compressed: started') await fs.rm(distDir, { recursive: true, force: true }) const sourceFiles = await walkFiles(srcDir) let minifiedCount = 0 let copiedCount = 0 for (const sourceFile of sourceFiles) { const relativePath = path.relative(srcDir, sourceFile) const outputFile = path.join(distDir, relativePath) await fs.mkdir(path.dirname(outputFile), { recursive: true }) if (path.extname(sourceFile) === '.mjs') { const sourceCode = await fs.readFile(sourceFile, 'utf8') const minifiedCode = await minifyJavaScript(sourceCode, relativePath) await fs.writeFile(outputFile, minifiedCode, 'utf8') minifiedCount++ continue } await fs.copyFile(sourceFile, outputFile) copiedCount++ } console.log(`build-compressed: done (minified=${minifiedCount}, copied=${copiedCount})`) } buildCompressed().catch((error) => { console.error('build-compressed: failed', error) process.exit(1) }) scripts/redis-bulk.lua000066400000000000000000000003541517716222300153210ustar00rootroot00000000000000-- -- Created by IntelliJ IDEA. -- User: patrikx3 -- Date: 7/9/20 -- Time: 4:59 PM -- To change this template use File | Settings | File Templates. -- for i = 1, 100000, 1 do redis.call("SET", "bulk-key-"..i, i) end return "Ok!" scripts/redis-cluster.sh000077500000000000000000000002311517716222300156730ustar00rootroot00000000000000#!/usr/bin/env bash docker run -e "IP=0.0.0.0" -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster:latestsrc/000077500000000000000000000000001517716222300116535ustar00rootroot00000000000000src/index.mjs000066400000000000000000000001701517716222300134730ustar00rootroot00000000000000import lib from './lib/index.mjs' import services from './service/index.mjs' export default { lib, services, } src/lib/000077500000000000000000000000001517716222300124215ustar00rootroot00000000000000src/lib/ai/000077500000000000000000000000001517716222300130125ustar00rootroot00000000000000src/lib/ai/prompt.mjs000066400000000000000000000544021517716222300150530ustar00rootroot00000000000000// Shared AI Redis-query core for both callers: // 1. redis-ui-server β€” agentic tool-use loop when the user supplies // their own Groq API key; otherwise proxies through network. // 2. network.corifeus.com /public/ai/redis-query β€” plain single-shot // chat completion (no tool execution here; tools are executed // in redis-ui-server which has the live Redis connection). // // What lives here (all shared): // - LANGUAGE_NAMES, buildLanguageInstruction // - SYSTEM_PROMPT, LIMITED_AI_SYSTEM_PROMPT, TOOL_USE_PROMPT // - buildSystemPrompt() // - cleanAiText(), parseAiResponse() // - estimateTokens(), summarizeMessages(), truncateToolContent() // - callGroq(), runSingleShotQuery() // // Each caller keeps only its own interface layer (Express req/res, // Socket.IO events, agentic loop, mongoose persistence, metrics). // // network.corifeus.com syncs a copy at build time via `yarn sync:prompt`. // Edit here only β€” never edit the synced copy in network. import Groq from 'groq-sdk'; // Map the 54 supported p3x-redis-ui GUI locale codes to human-readable language // names. Naming the language by code alone (e.g. "en") sometimes causes // Groq/oss-120b to default to whichever language the developer mentioned most // in the prompt (Hungarian, in our case). export const LANGUAGE_NAMES = { ar: 'Arabic', az: 'Azerbaijani', be: 'Belarusian', bg: 'Bulgarian', bn: 'Bengali', bs: 'Bosnian', cs: 'Czech', da: 'Danish', de: 'German', el: 'Greek', en: 'English', es: 'Spanish', et: 'Estonian', fi: 'Finnish', fil: 'Filipino', fr: 'French', he: 'Hebrew', hr: 'Croatian', hu: 'Hungarian', hy: 'Armenian', id: 'Indonesian', it: 'Italian', ja: 'Japanese', ka: 'Georgian', kk: 'Kazakh', km: 'Khmer', ko: 'Korean', ky: 'Kyrgyz', lt: 'Lithuanian', mk: 'Macedonian', ms: 'Malay', ne: 'Nepali', nl: 'Dutch', no: 'Norwegian', pl: 'Polish', 'pt-BR': 'Brazilian Portuguese', 'pt-PT': 'Portuguese', ro: 'Romanian', ru: 'Russian', si: 'Sinhala', sk: 'Slovak', sl: 'Slovenian', sr: 'Serbian', sv: 'Swedish', sw: 'Swahili', ta: 'Tamil', tg: 'Tajik', th: 'Thai', tr: 'Turkish', uk: 'Ukrainian', vi: 'Vietnamese', 'zh-HK': 'Traditional Chinese (Hong Kong)', 'zh-TW': 'Traditional Chinese (Taiwan)', zn: 'Simplified Chinese', }; export function buildLanguageInstruction(context) { const code = context?.uiLanguage; if (code) { const name = LANGUAGE_NAMES[code] || code; return `\n\n# Response Language\nThe user's GUI is in ${name} (locale: ${code}). You MUST write the explanation (after the --- separator) in ${name}, regardless of what language the user types their prompt in. Only use ${name} β€” do not switch to any other language.`; } return `\n\n# Response Language\nYou MUST write the explanation (after the --- separator) in the same language as the user's prompt. Match the language of the user's input exactly β€” if they write in English, respond in English.`; } export const SYSTEM_PROMPT = `You are an expert Redis command generator embedded in a Redis GUI console. Users type natural language in any human language (English, Hungarian, Chinese, etc.) and you translate it into valid Redis CLI commands. # Output Format One or more Redis commands (one per line), then a separator, then an explanation: \`\`\` COMMAND1 COMMAND2 --- Brief explanation in the user's language \`\`\` - For simple requests: output a single command line - For complex requests needing multiple steps: output multiple command lines (one per line) - For bulk operations: prefer a single EVAL script, but use multiple commands if clearer - The --- separator is REQUIRED between commands and explanation - The explanation language is specified in the "Response Language" section at the end of this prompt β€” follow it exactly # Core Principles 1. Generate ONLY real, valid Redis commands that a Redis server will accept 2. Never invent key names, index names, or field names β€” use only what is provided in context or use wildcard patterns 3. The user's Redis GUI will execute your command directly β€” it must be syntactically correct 4. Support all human languages as input β€” always output a Redis command regardless of input language # Command Selection Guide ## Key Discovery & Listing - "show all keys" / "list keys" β†’ KEYS * - "find keys matching user" β†’ KEYS user:* - "keys starting with session" β†’ KEYS session:* - "how many keys" β†’ DBSIZE ## Key Type Filtering When user asks for keys of a specific data type, use SCAN with TYPE filter: - "show all hash keys" β†’ SCAN 0 MATCH * TYPE hash COUNT 10000 - "show all json keys" / "rejson keys" β†’ SCAN 0 MATCH * TYPE ReJSON-RL COUNT 10000 - "show all set keys" β†’ SCAN 0 MATCH * TYPE set COUNT 10000 - "show all list keys" β†’ SCAN 0 MATCH * TYPE list COUNT 10000 - "show all string keys" β†’ SCAN 0 MATCH * TYPE string COUNT 10000 - "show all stream keys" β†’ SCAN 0 MATCH * TYPE stream COUNT 10000 - "show all sorted set keys" β†’ SCAN 0 MATCH * TYPE zset COUNT 10000 - For checking a single key's type β†’ TYPE keyname Note: SCAN returns [cursor, [keys...]]. cursor=0 means scan complete. ## Reading Values - String: GET key - Hash: HGETALL key | HGET key field - List: LRANGE key 0 -1 - Set: SMEMBERS key - Sorted Set: ZRANGE key 0 -1 WITHSCORES - Stream: XRANGE key - + - JSON/ReJSON: JSON.GET key $ | JSON.GET key $.fieldname - Multiple strings: MGET key1 key2 - Multiple JSON: JSON.MGET key1 key2 $ ## Writing Values - String: SET key value [EX seconds] - Hash: HSET key field value [field value ...] - List: LPUSH/RPUSH key value [value ...] - Set: SADD key member [member ...] - Sorted Set: ZADD key score member [score member ...] - Stream: XADD key * field value [field value ...] - JSON: JSON.SET key $ 'jsonvalue' ## Key Management - Delete: DEL key [key ...] - Rename: RENAME key newkey - TTL check: TTL key | PTTL key - Set expiry: EXPIRE key seconds | PEXPIRE key ms - Persist (remove TTL): PERSIST key - Check existence: EXISTS key [key ...] ## Server & Info - Server info: INFO [section] (sections: server, clients, memory, stats, replication, cpu, modules, keyspace, all) - Memory usage: MEMORY USAGE key | INFO memory - Connected clients: CLIENT LIST - Config: CONFIG GET parameter - Slow log: SLOWLOG GET [count] - Database size: DBSIZE - Flush database: FLUSHDB - Flush all: FLUSHALL - Last save: LASTSAVE - Server time: TIME ## RediSearch (only when explicitly requested) - Search: FT.SEARCH indexname query - List indexes: FT._LIST - Index info: FT.INFO indexname - Aggregate: FT.AGGREGATE indexname query - Create index: FT.CREATE indexname ON HASH PREFIX 1 prefix: SCHEMA field TYPE ... - Drop index: FT.DROPINDEX indexname ## Pub/Sub - Publish: PUBLISH channel message - Subscribe: SUBSCRIBE channel ## Cluster - Cluster info: CLUSTER INFO - Cluster nodes: CLUSTER NODES - CLUSTER SLOTS / CLUSTER SHARDS β€” slot distribution ## Multi-step operations β€” PREFER multiple commands over EVAL When the user needs multiple Redis operations, output them as separate commands (one per line): - SET test:str hello - HSET test:hash f1 v1 f2 v2 - RPUSH test:list a b c This is ALWAYS preferred over EVAL unless a loop is needed. ## Scripting (EVAL) β€” ONLY for loops or atomic operations Use EVAL ONLY when a loop or atomicity is required (e.g. "generate 100 random keys"): - EVAL "lua_script" numkeys [key ...] [arg ...] - Write Lua code with REAL line breaks inside the quotes β€” the console supports multi-line input - NEVER use literal \\n escape sequences β€” they cause Redis script compilation errors - CORRECT example: EVAL " for i=1,3 do redis.call('SET','k'..i,i) end return 'done' " 0 - WRONG: EVAL "for i=1,3 do\\nredis.call('SET','k'..i,i)\\nend" 0 ### EVAL in Cluster Mode β€” CRITICAL In Redis Cluster, ALL keys accessed inside a single EVAL script MUST hash to the SAME slot. To achieve this, use a hash tag in every key name: the part inside {braces} determines the slot. Example: keys \`foo:{tag}:1\`, \`bar:{tag}:2\`, \`baz:{tag}:3\` all hash to the same slot because they share \`{tag}\`. - ALWAYS include a hash tag like \`{data}\` in key names when generating EVAL scripts for cluster mode - Example: \`redis.call('SET', 'random-string:{data}:'..i, value)\` β€” all keys go to the same slot - Without a hash tag, the script WILL fail with "Script attempted to access a non local key" - This applies to ALL EVAL scripts in cluster mode, no exceptions # Redis Type Names (for TYPE command responses) - string, list, set, zset, hash, stream, ReJSON-RL, TSDB-TYPE (TimeSeries) - MBbloom-- (Bloom filter), MBbloomCF (Cuckoo filter), TopK-TYPE (Top-K), CMSk-TYPE (Count-Min Sketch), TDIS-TYPE (T-Digest) - vectorset (VectorSet) ## RedisBloom (Bloom filter, Cuckoo filter, Top-K, Count-Min Sketch, T-Digest) - Bloom filter info: BF.INFO key - Add to bloom: BF.ADD key item - Check bloom: BF.EXISTS key item - Create bloom: BF.RESERVE key error_rate capacity - Cuckoo filter info: CF.INFO key - Add to cuckoo: CF.ADD key item - Check cuckoo: CF.EXISTS key item - Delete from cuckoo: CF.DEL key item - Create cuckoo: CF.RESERVE key capacity - Top-K info: TOPK.INFO key - Add to top-k: TOPK.ADD key item [item ...] - List top-k: TOPK.LIST key WITHCOUNT - Create top-k: TOPK.RESERVE key topk [width] [depth] [decay] - Count-Min Sketch info: CMS.INFO key - Increment CMS: CMS.INCRBY key item increment - Query CMS: CMS.QUERY key item [item ...] - Create CMS: CMS.INITBYDIM key width depth - T-Digest info: TDIGEST.INFO key - Add to T-Digest: TDIGEST.ADD key value [value ...] - Query quantile: TDIGEST.QUANTILE key quantile [quantile ...] - Create T-Digest: TDIGEST.CREATE key [COMPRESSION compression] - "show all bloom keys" β†’ SCAN 0 MATCH * TYPE MBbloom-- COUNT 10000 - "show all cuckoo keys" β†’ SCAN 0 MATCH * TYPE MBbloomCF COUNT 10000 ## VectorSet (Redis 8) - Add vector: VADD key VALUES dim v1 v2 ... element [SETATTR "field\\nvalue\\nfield\\nvalue"] - Get similar: VSIM key VALUES dim v1 v2 ... [COUNT count] - Get similar by element: VSIM key ELE element [COUNT count] - Card: VCARD key - Dimensions: VDIM key - Get attributes: VGETATTR key element - Set attributes: VSETATTR key element "field\\nvalue" - Remove element: VREM key element - Info: VINFO key - List elements: VLINKS key - "show all vector keys" β†’ SCAN 0 MATCH * TYPE vectorset COUNT 10000 - VSIM with filter (Redis 8.2+): VSIM key ELE element COUNT 10 FILTER "attr == 'value'" ## Redis 8.0+ Hash Per-Field TTL - Get with expiry: HGETEX key FIELDS 1 field EX seconds - Set with expiry: HSETEX key FIELDS 1 field value EX seconds - Get and delete: HGETDEL key FIELDS 1 field - "set hash field with TTL" β†’ HSETEX key FIELDS 1 myfield myvalue EX 3600 - "get hash field and set expiry" β†’ HGETEX key FIELDS 1 myfield EX 300 ## Redis 8.2+ Stream Commands - Delete with consumer group: XDELEX key id [GROUP group] ## Redis 8.4+ Commands - Set multiple with expiry: MSETEX key1 val1 key2 val2 EX 3600 - Hash digest: DIGEST key - Hybrid search: FT.HYBRID index "query" VECTOR field 10 vector_blob LIMIT 0 10 ## Redis 8.6+ Stream Commands - Stream IDMP config: XCFGSET key parameter value # Critical Rules - NEVER use FT.SEARCH or FT.AGGREGATE unless the user explicitly mentions "search index", "full-text search", "FT.", or "RediSearch" - NEVER fabricate key names β€” if unsure, use patterns like KEYS * or KEYS prefix:* - NEVER fabricate index names β€” if indexes are provided in context, use those exact names - When the user mentions "rejson", "json keys", or "JSON type", they mean keys stored with the RedisJSON module - Prefer simple commands β€” KEYS over SCAN for readability in a GUI console - If the user asks something that needs multiple steps, output multiple commands (one per line) ## Bash Pipe Integration via EVAL Lua If the user's input contains bash-style pipe operators (e.g. \`| head -20\`, \`| tail -5\`, \`| grep pattern\`, \`| sort\`, \`| wc -l\`, \`| uniq\`, \`| awk\`, \`| sed\`), convert the ENTIRE command including all pipe operations into a single Redis EVAL Lua script. - Use only valid Redis Lua API: redis.call, cjson, table, string, math - Always return the result from the script, never use print - Write Lua code with REAL line breaks β€” NEVER use literal \\n escape sequences - Strip any \`redis-cli\` prefix from the input Example input: "keys ratingbet* | head -20 | sort" Example output: EVAL " local keys = redis.call('KEYS', 'ratingbet*') table.sort(keys) local result = {} for i = 1, math.min(20, #keys) do result[#result+1] = keys[i] end return result " 0 --- Retrieves keys matching ratingbet*, sorts them alphabetically and returns the first 20`; export const LIMITED_AI_SYSTEM_PROMPT = `You are the p3x-redis-ui assistant in LIMITED MODE. The user is NOT currently connected to any Redis server β€” so you have no live state to inspect and no index, key, or module information to draw on. You CAN answer: - General Redis knowledge questions ("what is ZADD?", "how does cluster failover work?", "explain Lua scripting in Redis") - Syntax help for any Redis command - Conceptual questions about Redis modules (RedisJSON, RediSearch, RedisTimeSeries, RedisBloom, Vector sets) - Lua/EVAL script authoring based on the user's description (output the script, do not execute it) - Generic "how do I" questions that don't need live data You MUST REFUSE and ask the user to connect first (via the GUI connection list) when they ask for: - "why is memory high?", "show my slow queries", "which clients are connected?" - any question that needs INFO, SLOWLOG, CLIENT LIST, MEMORY STATS, CONFIG, DBSIZE, SCAN - "describe key X", "find keys with TTL < Y", "show the biggest hash" - anything that presumes a live connection # Output Format One or more Redis commands (one per line), then a separator, then an explanation: \`\`\` COMMAND1 --- Brief explanation in the user's language \`\`\` If no command is appropriate (pure explanation, or refusal of a live-state question), output only the --- separator followed by the explanation.`; // Appended to SYSTEM_PROMPT when the consumer can actually run tool calls // (only redis-ui-server's agentic loop can β€” network proxies a plain chat). export const TOOL_USE_PROMPT = `\n\n# Tool use β€” live state inspection You have tools (redis_info, redis_memory_stats, redis_slowlog_get, redis_client_list, redis_config_get, redis_dbsize, redis_latency_latest, redis_scan, redis_type, redis_ttl, redis_memory_usage, redis_cluster_info, redis_cluster_nodes, redis_acl_whoami, redis_module_list) that run read-only Redis commands against the user's connection and return live results. When to use tools: - "why is memory high?" β†’ call redis_info(section="memory") and redis_memory_stats, then explain - "show slow queries" β†’ call redis_slowlog_get, then summarise - "who is connected?" β†’ call redis_client_list, then summarise - "what is maxmemory set to?" β†’ call redis_config_get(pattern="maxmemory*"), then answer - "how many keys per database?" β†’ call redis_info(section="keyspace") + redis_dbsize - Diagnostics, metrics, live state β†’ tools first, answer second When NOT to use tools: - "what does ZADD do?" / "write a lua script to …" β†’ answer from general knowledge, no tools - Command-generation requests ("delete key foo", "set key bar to 1") β†’ just output the command, do NOT execute it - Anything the user can see or do themselves β€” don't burn tokens on redundant tool calls After tool calls, return the final answer in the normal Output Format (commands + "---" + explanation). The explanation should summarise what the tool results show, not dump raw output.`; // Strip markdown the model sometimes emits so the console renders plain text // that matches regular command output styling: // - leading/trailing --- separators // - ```lang ... ``` fences // - stray **bold** and `inline code` wrapping export function cleanAiText(s) { if (typeof s !== 'string') return ''; let out = s; out = out.replace(/^\s*-{3,}\s*/g, '').replace(/\s*-{3,}\s*$/g, ''); out = out.replace(/```[a-zA-Z0-9_-]*\n?([\s\S]*?)\n?```/g, '$1'); out = out.replace(/```/g, ''); out = out.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/(? line.trim().length > 0); return { command: lines[0] || '', explanation: lines.slice(1).join('\n') || '', }; } // Unified builder shared by both callers. // Pass `{ includeToolUse: true }` from redis-ui-server; omit from network (no tools available there). export function buildSystemPrompt(context, { includeToolUse = false } = {}) { if (context && (context.connectionState === 'none' || context.connectionState === 'connecting')) { return LIMITED_AI_SYSTEM_PROMPT + buildLanguageInstruction(context); } let prompt = SYSTEM_PROMPT; if (includeToolUse && context?.connectionState === 'connected') { prompt += TOOL_USE_PROMPT; } if (context) { const parts = []; if (context.redisVersion) parts.push(`Redis version: ${context.redisVersion}`); if (context.redisMode) parts.push(`Mode: ${context.redisMode}`); if (context.usedMemory) parts.push(`Memory: ${context.usedMemory}`); if (context.connectedClients) parts.push(`Clients: ${context.connectedClients}`); if (context.os) parts.push(`OS: ${context.os}`); if (context.modules) parts.push(`Loaded modules: ${JSON.stringify(context.modules)}`); if (context.databases && context.databases.length > 0) parts.push(`Databases: ${context.databases.join(', ')}`); if (context.connectionName) parts.push(`Connection name: ${context.connectionName}`); if (context.currentDatabase !== undefined) parts.push(`Current database: ${context.currentDatabase}`); if (context.currentPage) parts.push(`Current GUI page: ${context.currentPage}`); if (parts.length > 0) { prompt += `\n\n# Connected Redis Server\n${parts.join('\n')}`; } if (context.indexes && context.indexes.length > 0) { prompt += `\n\nAvailable RediSearch indexes: ${context.indexes.join(', ')}`; } if (context.schema) { prompt += `\n\nSchema information: ${JSON.stringify(context.schema)}`; } if (context.keyPatterns && context.keyPatterns.length > 0) { prompt += `\n\nKey patterns in use: ${context.keyPatterns.join(', ')}`; } prompt += buildLanguageInstruction(context); } return prompt; } // ─── Generic utilities ──────────────────────────────────────────────────── // Rough token count from character count (Groq/OpenAI chat completions use ~4 chars per token). export function estimateTokens(chars) { return Math.ceil(chars / 4); } // Summarise a messages[] array for logging: total chars, role breakdown, tool-call count. export function summarizeMessages(messages) { if (!Array.isArray(messages)) return { count: 0, chars: 0, roles: {}, toolCalls: 0 }; let chars = 0; const roles = {}; let toolCalls = 0; for (const m of messages) { roles[m.role] = (roles[m.role] || 0) + 1; if (typeof m.content === 'string') chars += m.content.length; if (Array.isArray(m.tool_calls)) { toolCalls += m.tool_calls.length; chars += JSON.stringify(m.tool_calls).length; } } return { count: messages.length, chars, roles, toolCalls }; } // Truncate a tool result string to avoid blowing the model's context window. // Default 8 KB (~2K tokens) matches redis-ui-server's original MAX_TOOL_RESULT_CHARS. export function truncateToolContent(content, maxChars = 8000) { const str = typeof content === 'string' ? content : String(content ?? ''); if (str.length <= maxChars) return str; const kept = str.slice(0, maxChars); return `${kept}\n... [truncated ${str.length - maxChars} chars β€” result too large for token budget]`; } // ─── Groq wrappers ──────────────────────────────────────────────────────── // Thin wrapper around Groq chat.completions.create. Caller provides the key, // model, and messages; tool_choice is only set when tools are provided. export async function callGroq({ messages, tools, apiKey, model, maxTokens, temperature = 0 }) { if (!apiKey) throw new Error('callGroq: apiKey is required'); const client = new Groq({ apiKey }); const payload = { model, messages, max_tokens: maxTokens, temperature, }; if (Array.isArray(tools) && tools.length > 0) { payload.tools = tools; payload.tool_choice = 'auto'; } return await client.chat.completions.create(payload); } // Single-shot redis-query: build system prompt, call Groq with [system, user], // parse the response into { command, explanation }. No tool use (that's the // agentic loop's job β€” only redis-ui-server runs it). Returns the parsed // fields plus usage + raw assistant message for the caller's bookkeeping. export async function runSingleShotQuery({ prompt, context, apiKey, model, maxTokens, temperature = 0, includeToolUse = false, }) { const systemPrompt = buildSystemPrompt(context, { includeToolUse }); const completion = await callGroq({ messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: String(prompt).trim() }, ], apiKey, model, maxTokens, temperature, }); const message = completion.choices?.[0]?.message || {}; const responseText = (message.content || '').trim(); return { ...parseAiResponse(responseText), usage: completion.usage || {}, assistantMessage: message, responseText, }; } src/lib/boot.mjs000066400000000000000000000034311517716222300141000ustar00rootroot00000000000000import 'corifeus-utils' import cli from './cli.mjs' import consoleStamp from './console-stamp.mjs' import httpService from '../service/http/index.mjs' import socketIoService from '../service/socket.io/index.mjs' const boot = async () => { global.p3xrs = {} p3xrs.cfg = undefined if (!(await cli())) { return; } consoleStamp() p3xrs.http = new httpService() await p3xrs.http.boot() p3xrs.socketIo = new socketIoService(); await p3xrs.socketIo.boot({ httpService: p3xrs.http }) p3xrs.redisConnections = {} p3xrs.redisConnectionsSubscriber = {} process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); }); const gracefulShutdown = async (signal) => { console.info(`Received ${signal}, shutting down gracefully...`) try { // Close Socket.IO connections if (p3xrs.socketIo?.socketio) { p3xrs.socketIo.socketio.close() } // Close all Redis connections if (p3xrs.redisConnections) { for (const key of Object.keys(p3xrs.redisConnections)) { try { if (p3xrs.redisConnections[key]?.ioredis) { p3xrs.redisConnections[key].ioredis.disconnect() } } catch {} } } // Close HTTP server if (p3xrs.http?.server) { p3xrs.http.server.close() } } catch (e) { console.error('Error during shutdown:', e) } process.exit(0) } process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) process.on('SIGINT', () => gracefulShutdown('SIGINT')) } export default boot src/lib/cli.mjs000066400000000000000000000322301517716222300137030ustar00rootroot00000000000000import path from 'path' import fs from 'fs' import os from 'os' import { program } from 'commander' import { parseBoolean, readPasswordHashFromFile } from './http-auth.mjs' const isPlainObject = (value) => { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } const mergeDeep = (target, source) => { const output = isPlainObject(target) ? { ...target } : {} if (!isPlainObject(source)) { return output } for (const [key, value] of Object.entries(source)) { if (Array.isArray(value)) { output[key] = value.slice() continue } if (isPlainObject(value)) { output[key] = mergeDeep(isPlainObject(output[key]) ? output[key] : {}, value) continue } output[key] = value } return output } const loadJsonFile = (filePath) => { if (!filePath || !fs.existsSync(filePath)) { return undefined } try { const content = fs.readFileSync(filePath, 'utf8') return JSON.parse(content) } catch (e) { console.warn(`Could not read config ${filePath}:`, e.message) return undefined } } const cli = async () => { const pkg = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) p3xrs.version = pkg.version program .version(pkg.version) .option('-c, --config [config]', 'Set the p3xr.json p3x-redis-ui-server configuration, see more help in p3x-redis-ui-server') .option('-r, --readonly-connections', 'Set the connections to be readonly, no adding, saving or delete a connection') .option('-n, --connections-file-name [filename]', 'Set the connections file name, overrides default .p3xrs-conns.json') .option('--http-auth-enable', 'Enable HTTP Basic auth') .option('--http-auth-disable', 'Disable HTTP Basic auth') .option('--http-auth-username [username]', 'HTTP Basic auth username') .option('--http-auth-password [password]', 'HTTP Basic auth plain password') .option('--http-auth-password-hash [hash]', 'HTTP Basic auth bcrypt password hash') .option('--http-auth-password-hash-file [file]', 'Read HTTP Basic auth bcrypt password hash from file') .option('--groq-api-key [key]', 'Groq API key for AI-powered Redis query translation (get a free key at console.groq.com)') .option('--groq-api-key-readonly', 'Prevent users from changing the Groq API key via the UI') .parse(process.argv); const programOptions = program.opts(); if (!process.versions.hasOwnProperty('electron') && !process.env.hasOwnProperty('P3XRS_DOCKER_HOME')) { if (!programOptions.config) { const findConfigFile = (startPath, filename) => { let currentPath = startPath; while (currentPath !== path.resolve(currentPath, '..')) { // Check until we reach the root directory const filePath = path.join(currentPath, filename); if (fs.existsSync(filePath)) { return filePath; } currentPath = path.resolve(currentPath, '..'); // Move up one directory level } throw new Error('The specified configuration file could not be found.'); } const resolveConfigPath = () => { // Attempt to find the config file starting from the directory of the main script or current directory const startPath = process.cwd(); return findConfigFile(startPath, 'p3xrs.json'); } programOptions.config = resolveConfigPath() // program.outputHelp() // return false } const configPath = path.resolve(process.cwd(), programOptions.config) //console.log(configPath) p3xrs.configPath = configPath p3xrs.cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')).p3xrs if (programOptions.readonlyConnections) { // console.warn(programOptions.readonlyConnections) p3xrs.cfg.readonlyConnections = true //console.warn(p3xrs.cfg.readonlyConnections === true) } if (typeof programOptions.groqApiKey === 'string' && programOptions.groqApiKey.trim()) { p3xrs.cfg.groqApiKey = programOptions.groqApiKey.trim() } if (programOptions.groqApiKeyReadonly) { p3xrs.cfg.groqApiKeyReadonly = true } if (typeof programOptions.connectionsFileName !== 'undefined' && programOptions.connectionsFileName) { // console.warn(programOptions.connectionsFileName) p3xrs.cfg.connectionsFileName = programOptions.connectionsFileName //console.warn(p3xrs.cfg.readonlyConnections === true) } } else { const defaultElectronConfig = { "http": { "port-info": "this is ommitted, it will be default 7843", "port": process.env.hasOwnProperty('P3XRS_DOCKER_HOME') ? 7843 : global.p3xrsElectronPort, "bind-info": "the interface with listen to, could be 127.0.0.1 or 0.0.0.0 or specific interface", "bind": "0.0.0.0", }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static": "~p3x-redis-ui-material/dist", "httpAuth": { "enabled": false, "username": "admin", "password": "", "passwordHash": "", }, "treeDividers": [ ":", "/", "|", "-", "@" ] } let electronUserDataDir = '' try { const electron = await import('electron') const electronApp = electron.default?.app || electron.app if (electronApp && typeof electronApp.getPath === 'function') { electronUserDataDir = electronApp.getPath('userData') } } catch (e) { electronUserDataDir = '' } const configuredDir = typeof process.env.P3XRS_ELECTRON_CONFIG_DIR === 'string' ? process.env.P3XRS_ELECTRON_CONFIG_DIR.trim() : '' const electronConfigDir = configuredDir || electronUserDataDir || os.homedir() p3xrs.configPath = path.resolve(electronConfigDir, 'p3xrs.json') let persistedRoot = loadJsonFile(p3xrs.configPath) if ((!persistedRoot || !isPlainObject(persistedRoot.p3xrs))) { const legacyConfigPath = path.resolve(process.cwd(), 'p3xrs.json') const legacyRoot = loadJsonFile(legacyConfigPath) if (legacyRoot && isPlainObject(legacyRoot.p3xrs)) { persistedRoot = legacyRoot } } const persistedConfig = persistedRoot && isPlainObject(persistedRoot.p3xrs) ? persistedRoot.p3xrs : {} p3xrs.cfg = mergeDeep(defaultElectronConfig, persistedConfig) if (programOptions.readonlyConnections) { p3xrs.cfg.readonlyConnections = true } else { p3xrs.cfg.readonlyConnections = false } if (typeof programOptions.groqApiKey === 'string' && programOptions.groqApiKey.trim()) { p3xrs.cfg.groqApiKey = programOptions.groqApiKey.trim() } if (programOptions.groqApiKeyReadonly) { p3xrs.cfg.groqApiKeyReadonly = true } } const applyHttpAuthConfig = () => { if (!p3xrs.cfg.httpAuth || typeof p3xrs.cfg.httpAuth !== 'object') { if (p3xrs.cfg.server && typeof p3xrs.cfg.server.httpAuth === 'object') { p3xrs.cfg.httpAuth = Object.assign({}, p3xrs.cfg.server.httpAuth) } else { p3xrs.cfg.httpAuth = {} } } const httpAuth = p3xrs.cfg.httpAuth if (typeof p3xrs.cfg.httpUser === 'string' && !httpAuth.username) { httpAuth.username = p3xrs.cfg.httpUser } if (typeof p3xrs.cfg.httpPassword === 'string' && !httpAuth.password) { httpAuth.password = p3xrs.cfg.httpPassword } if (typeof process.env.HTTP_USER === 'string' && process.env.HTTP_USER.trim() !== '') { httpAuth.username = process.env.HTTP_USER.trim() } if (typeof process.env.HTTP_PASSWORD === 'string') { httpAuth.password = process.env.HTTP_PASSWORD } if (typeof process.env.HTTP_PASSWORD_HASH === 'string' && process.env.HTTP_PASSWORD_HASH.trim() !== '') { httpAuth.passwordHash = process.env.HTTP_PASSWORD_HASH.trim() } if (typeof process.env.HTTP_PASSWORD_HASH_FILE === 'string' && process.env.HTTP_PASSWORD_HASH_FILE.trim() !== '') { const hashFromFile = readPasswordHashFromFile(process.env.HTTP_PASSWORD_HASH_FILE) if (hashFromFile) { httpAuth.passwordHash = hashFromFile } } const envEnabled = parseBoolean(process.env.HTTP_AUTH_ENABLED) if (envEnabled !== undefined) { httpAuth.enabled = envEnabled } if (typeof programOptions.httpAuthUsername === 'string' && programOptions.httpAuthUsername.trim() !== '') { httpAuth.username = programOptions.httpAuthUsername.trim() } if (typeof programOptions.httpAuthPassword === 'string') { httpAuth.password = programOptions.httpAuthPassword } if (typeof programOptions.httpAuthPasswordHash === 'string' && programOptions.httpAuthPasswordHash.trim() !== '') { httpAuth.passwordHash = programOptions.httpAuthPasswordHash.trim() } if (typeof programOptions.httpAuthPasswordHashFile === 'string' && programOptions.httpAuthPasswordHashFile.trim() !== '') { const hashFromCliFile = readPasswordHashFromFile(programOptions.httpAuthPasswordHashFile) if (hashFromCliFile) { httpAuth.passwordHash = hashFromCliFile } } if (programOptions.httpAuthEnable === true) { httpAuth.enabled = true } if (programOptions.httpAuthDisable === true) { httpAuth.enabled = false } } applyHttpAuthConfig() const authLog = p3xrs.cfg && p3xrs.cfg.httpAuth && typeof p3xrs.cfg.httpAuth === 'object' ? p3xrs.cfg.httpAuth : {} const authEnabled = parseBoolean(authLog.enabled) === true const authHasHash = typeof authLog.passwordHash === 'string' && authLog.passwordHash.trim().length > 0 const authHasPlain = typeof authLog.password === 'string' && authLog.password.length > 0 console.info(`http auth: ${authEnabled ? 'enabled' : 'disabled'} (user=${authLog.username || 'admin'}, hash=${authHasHash ? 'set' : 'empty'}, plain=${authHasPlain ? 'set' : 'empty'})`) if (p3xrs.cfg.connectionsFileName === undefined) { p3xrs.cfg.connectionsFileName = '.p3xrs-conns.json' } if (!p3xrs.cfg.hasOwnProperty('static')) { p3xrs.cfg.static = '~p3x-redis-ui-material/dist' } // staticReact: no default β€” auto-detected from static path in http service // staticVue: no default β€” auto-detected from static path in http service if (!p3xrs.cfg.hasOwnProperty('connections')) { p3xrs.cfg.connections = {} } if (!p3xrs.cfg.connections.hasOwnProperty('home-dir')) { p3xrs.cfg.connections = 'home' } if (p3xrs.cfg.connections['home-dir'] === 'home') { p3xrs.cfg.connections['home-dir'] = os.homedir(); } if (process.env.hasOwnProperty('P3XRS_DOCKER_HOME')) { p3xrs.cfg.connections['home-dir'] = process.env.P3XRS_DOCKER_HOME } if (process.env.FLATPAK_ID) { // process.env.XDG_DATA_HOME p3xrs.cfg.connections['home-dir'] = '/var/data/' } if (process.env.hasOwnProperty('P3XRS_PORT')) { p3xrs.cfg.http.port = process.env.P3XRS_PORT } p3xrs.cfg.connections['home'] = path.resolve(p3xrs.cfg.connections['home-dir'], p3xrs.cfg.connectionsFileName) console.info('using home config is', p3xrs.cfg.connections['home']) if (!fs.existsSync(p3xrs.cfg.connections.home)) { fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify({ update: new Date(), list: [], }, null, 4)) } p3xrs.connections = JSON.parse(fs.readFileSync(p3xrs.cfg.connections.home, 'utf8')) //console.log(p3xrs.cfg.connections.home, p3xrs.connections) //console.log(p3xrs.connections) /* p3xrs.redis = {} let keyStreamPaging = 10000 Object.defineProperty(p3xrs.redis, 'key-stream-paging', { get: () => { return keyStreamPaging }, set: (value) => { keyStreamPaging = value } }) */ return true; } export default cli; src/lib/console-stamp.mjs000066400000000000000000000026371517716222300157300ustar00rootroot00000000000000import chalk from 'chalk' const consoleStamp = () => { // overriding the console should be after this!!! console.warn = console.log const methods = ['log', 'info', 'warn', 'error', 'debug'] const originalMethods = {} for(let method of methods) { originalMethods[method] = console[method] console[method] = function() { if (arguments[0]) { let label switch(method) { case 'error': label = chalk.bold.red(method.toUpperCase()); break; case 'warn': label = chalk.bold.blue(method.toUpperCase()); break; default: label = chalk.green(method.toUpperCase()); } let data = '' //chalk`${moment().format(`YYYY/MM/DD HH:mm:ss.SSS`)} ` data += chalk.gray('[P3XRS]') + ` [PID: ${(String(process.pid).padStart(6, 0))}] [${label.padStart(5, ' ')}]: ` //arguments[0] = data + arguments[0] const mainArguments = Array.prototype.slice.call(arguments); mainArguments.unshift(data); originalMethods[method].apply(null, mainArguments) } else { originalMethods[method].apply(null, arguments) } } } } export default consoleStamp src/lib/http-auth.mjs000066400000000000000000000127501517716222300150570ustar00rootroot00000000000000import fs from 'fs' import crypto from 'crypto' import bcrypt from 'bcryptjs' const parseBoolean = (value) => { if (typeof value === 'boolean') { return value } if (typeof value !== 'string') { return undefined } const normalized = value.trim().toLowerCase() if (['1', 'true', 'yes', 'on'].includes(normalized)) { return true } if (['0', 'false', 'no', 'off'].includes(normalized)) { return false } return undefined } const resolveConfiguredHttpAuth = () => { const cfg = p3xrs && p3xrs.cfg && typeof p3xrs.cfg === 'object' ? p3xrs.cfg : {} const fromServer = cfg.server && typeof cfg.server.httpAuth === 'object' ? cfg.server.httpAuth : {} const fromRoot = cfg.httpAuth && typeof cfg.httpAuth === 'object' ? cfg.httpAuth : {} const merged = Object.assign({}, fromServer, fromRoot) const username = typeof merged.username === 'string' && merged.username.length > 0 ? merged.username : 'admin' const password = typeof merged.password === 'string' ? merged.password : '' const passwordHash = typeof merged.passwordHash === 'string' ? merged.passwordHash.trim() : '' const enabledRaw = parseBoolean(merged.enabled) const hasSecret = password.length > 0 || passwordHash.length > 0 const enabled = enabledRaw === undefined ? hasSecret : enabledRaw return { enabled, username, password, passwordHash, } } const safeCompare = (a, b) => { const aBuffer = Buffer.from(typeof a === 'string' ? a : '', 'utf8') const bBuffer = Buffer.from(typeof b === 'string' ? b : '', 'utf8') if (aBuffer.length !== bBuffer.length) { return false } return crypto.timingSafeEqual(aBuffer, bBuffer) } const parseBasicAuthorizationHeader = (headerValue) => { if (typeof headerValue !== 'string' || headerValue.length === 0) { return null } const parts = headerValue.split(' ') if (parts.length !== 2 || parts[0].toLowerCase() !== 'basic') { return null } const decoded = Buffer.from(parts[1], 'base64').toString('utf8') const colonIndex = decoded.indexOf(':') if (colonIndex === -1) { return null } return { username: decoded.slice(0, colonIndex), password: decoded.slice(colonIndex + 1), } } const verifyCredentials = ({ username, password }) => { const settings = resolveConfiguredHttpAuth() if (!settings.enabled) { return true } if (!safeCompare(username, settings.username)) { return false } if (settings.passwordHash.length > 0) { try { return bcrypt.compareSync(password, settings.passwordHash) } catch (e) { return false } } if (settings.password.length > 0) { return safeCompare(password, settings.password) } return false } const verifyAuthorizationHeader = (headerValue) => { const settings = resolveConfiguredHttpAuth() if (!settings.enabled) { return true } const parsed = parseBasicAuthorizationHeader(headerValue) if (!parsed) { return false } return verifyCredentials(parsed) } const readPasswordHashFromFile = (filename) => { if (typeof filename !== 'string' || filename.trim() === '') { return '' } const resolved = filename.trim() if (!fs.existsSync(resolved)) { return '' } try { return fs.readFileSync(resolved, 'utf8').trim() } catch (e) { return '' } } // JWT token support (HS256, no external dependency) let _jwtSecret = null const getJwtSecret = () => { if (_jwtSecret) return _jwtSecret const settings = resolveConfiguredHttpAuth() const source = settings.passwordHash || settings.password || crypto.randomBytes(32).toString('hex') _jwtSecret = crypto.createHash('sha256').update('p3xrs-jwt-' + source).digest() return _jwtSecret } const createAuthToken = (username) => { const secret = getJwtSecret() const payload = { sub: username, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 86400, // 24 hours } const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url') const body = Buffer.from(JSON.stringify(payload)).toString('base64url') const signature = crypto.createHmac('sha256', secret).update(`${header}.${body}`).digest('base64url') return `${header}.${body}.${signature}` } const verifyAuthToken = (token) => { if (typeof token !== 'string' || token.length === 0) return null try { const secret = getJwtSecret() const parts = token.split('.') if (parts.length !== 3) return null const [header, body, signature] = parts const expected = crypto.createHmac('sha256', secret).update(`${header}.${body}`).digest('base64url') const sigBuf = Buffer.from(signature, 'utf8') const expBuf = Buffer.from(expected, 'utf8') if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) return null const payload = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) return null return payload } catch (e) { return null } } const resetJwtSecret = () => { _jwtSecret = null } export { parseBoolean, resolveConfiguredHttpAuth, verifyCredentials, verifyAuthorizationHeader, readPasswordHashFromFile, createAuthToken, verifyAuthToken, resetJwtSecret, } src/lib/index.mjs000066400000000000000000000002421517716222300142410ustar00rootroot00000000000000import boot from './boot.mjs' import cli from './cli.mjs' import consoleStamp from './console-stamp.mjs' export default { boot, cli, consoleStamp, } src/lib/ioredis-cluster/000077500000000000000000000000001517716222300155365ustar00rootroot00000000000000src/lib/ioredis-cluster/cluster.mjs000066400000000000000000000137541517716222300177440ustar00rootroot00000000000000import Redis from 'ioredis' import { EventEmitter } from 'events' import redisInfo from './redis-info.mjs' import setDefaultOptionsFromServer from './set-default-options-from-server.mjs' export default class Cluster extends Redis.Cluster { constructor(server, options = {}) { server = Array.isArray(server) ? server : [server] options = setDefaultOptionsFromServer(options, server) options.clusterRetryStrategy = null super(server, options) } /* async infoObject(...args) { const [key] = args if (key === 'keyspace') { return { databases: [await this._getClusterInfoKeyspace()] } } const info = await this.info(...args) const infoObject = redisInfo.parse(info) if (!key) { infoObject.databases[0] = await this._getClusterInfoKeyspace() } return infoObject } */ async _getClusterInfoKeyspace(info) { const keyspaceList = await Promise.all(this.nodes('master').map(node => { return node.info('keyspace') })) let keys = 0 let expires = 0 let avg_ttl = 0 for (const nodeKeyspace of keyspaceList) { if (!nodeKeyspace) { continue } const parsed = redisInfo.parse(nodeKeyspace) const db0 = parsed.databases[0] if (!db0) { continue } const { keys: nodeKeys = 0, expires: nodeExpires = 0, avg_ttl: nodeAvgTtl = 0, } = db0 keys += nodeKeys expires += nodeExpires avg_ttl += nodeAvgTtl } avg_ttl = avg_ttl ? Math.round(avg_ttl / expires) : 0 const clusterKeyspace = { keys, expires, avg_ttl, } // console.log({clusterKeyspace}) return clusterKeyspace } async originalDbsize(...args) { return super.dbsize(...args) } async dbsize() { const nodeCounts = await Promise.all(this.nodes('master').map(node => { return node.dbsize() })) const count = nodeCounts.reduce((tt, c) => tt + c, 0) return count } async flushdb(...args) { await Promise.all(this.nodes('master').map(node => node.flushdb(...args))) return 'OK' } async flushall(...args) { await Promise.all(this.nodes('master').map(node => node.flushall(...args))) return 'OK' } // ACL commands must broadcast to all master nodes in cluster mode async aclSetuser(...args) { await Promise.all(this.nodes('master').map(node => node.call('ACL', 'SETUSER', ...args))) return 'OK' } async aclDeluser(...args) { await Promise.all(this.nodes('master').map(node => node.call('ACL', 'DELUSER', ...args))) return 'OK' } originalRename(...args) { return super.rename(...args) } async rename(key, keyNew, callback) { let res = null let err = null try { let [value, ttl] = await Promise.all([ this.dumpBuffer(key), this.ttl(key), ]) ttl = parseInt(ttl) if (ttl < 0) { ttl = 0 } await this.del(keyNew) await this.restore(keyNew, ttl, value) await this.del(key) res = 'OK' } catch (e) { err = e } if (typeof callback === 'function') { callback(err, res) } else if (err) { throw err } return res } originalPipeline(...args) { return super.pipeline(...args) } pipeline(...pipelineArgs) { const calls = [] async function exec(calls) { const results = [] for (let c of calls) { const result = await c() results.push(result) } // console.log({results}) return results } const proxy = new Proxy(calls, { get: (calls, method) => { return (...callArgs) => { switch (method) { case 'exec': return exec(calls) break } const callback = async () => { let err = null let result = null try { result = await this[method](...callArgs) } catch (e) { err = e } return [err, result] } calls.push(callback) return proxy } } }) return proxy } scanStream(...args) { const stream = new EventEmitter() this._streamNodes({ stream, method: 'scanStream', params: args, }) return stream } async _streamNodes(options = {}) { let { nodes = this.nodes('master'), stream = new EventEmitter(), method, params = [], } = options try { const promises = [] for (let node of nodes) { promises.push( new Promise((resolve, reject) => { const nodeStream = node[method](...params) nodeStream.on('data', (resultKeys) => { // console.log({resultKeys}) stream.emit('data', resultKeys) }) nodeStream.on('end', () => { resolve() }) }) ) } await Promise.all(promises) } finally { stream.emit('end') } } } src/lib/ioredis-cluster/create-with-cluster-auto-detect.mjs000066400000000000000000000012061517716222300243570ustar00rootroot00000000000000import Redis from 'ioredis' import isClusterEnabled from './is-cluster-enabled.mjs' import Cluster from './cluster.mjs' import setDefaultOptionsFromServer from './set-default-options-from-server.mjs' export default async function createWithClusterAutoDetect(server, options = {}) { let isCluster if (Array.isArray(server)) { isCluster = true } else { isCluster = await isClusterEnabled(server, true) } if (!isCluster) { return new Redis(server) } // server = await getClusterNodes(server) options = setDefaultOptionsFromServer(options, server) return new Cluster(server, options) } src/lib/ioredis-cluster/get-cluster-nodes.mjs000066400000000000000000000036341517716222300216230ustar00rootroot00000000000000import Redis from 'ioredis' export default async function getClusterNodes(servers, options = {}) { if (!Array.isArray(servers)) { servers = [servers] } const errors = [] let nodes for (const server of servers) { let redis try { redis = new Redis({...server, retryStrategy: () => false}) const rawNodes = await new Promise((resolve, reject) => { redis.sendCommand( new Redis.Command( 'CLUSTER', ['NODES'], 'utf-8', function (err, value) { if (err) reject(err) else resolve(value.toString()) } ) ) }) const lines = rawNodes.trim().split("\n") nodes = lines.reduce((arr, line) => { if (!line) { return arr } const row = line.split(' ') const [ node_id, server, flags, ] = row const [target, slots] = server.split('@') const [host, port] = target.split(':') const node = { host, port, } arr.push(node) return arr }, []) return nodes } catch (error) { console.error(error) errors.push(error) } finally { redis.disconnect() } if (nodes) { break } } if (nodes) { return nodes } const errorsMsg = errors.map(e => e.toString()).join(', ') throw new Error('getClusterNodes errors: ' + errorsMsg) } src/lib/ioredis-cluster/get-info.mjs000066400000000000000000000004441517716222300177630ustar00rootroot00000000000000import Redis from 'ioredis' import redisInfo from './redis-info.mjs' export default async function getInfo(server, options = {}) { const redis = new Redis(server) const rawInfo = await redis.info() redis.disconnect() const info = redisInfo.parse(rawInfo) return info } src/lib/ioredis-cluster/index.mjs000066400000000000000000000000651517716222300173610ustar00rootroot00000000000000import Redis from './redis.mjs' export default Redis src/lib/ioredis-cluster/is-cluster-enabled.mjs000066400000000000000000000003331517716222300217320ustar00rootroot00000000000000import getInfo from './get-info.mjs' export default async function isClusterEnabled(server, cache = false) { const {cluster_enabled} = await getInfo(server, {cache}) return Boolean(parseInt(cluster_enabled)) } src/lib/ioredis-cluster/redis-info.mjs000066400000000000000000000055761517716222300203250ustar00rootroot00000000000000/* from npm's redis-info, adding missing avg_ttl */ import { fromPairs, find, has } from 'lodash-es' export default { parse: function (info) { return parseFields(splitStr(info)) } } function startWith(pattern) { return function (value) { return value.indexOf(pattern) === 0 } } function split(s) { return function (v) { return v.split(s) } } function orEmptyStr(v) { return v || '' } function takeN(func, n) { return function (v) { return func(v[n]) } } function takeFirst(func) { return takeN(func, 0) } /** * Split the info string by \n and : * @param {String} str the returned redis info * @return {Array} Array of [key, value] */ function splitStr(str) { return str.split('\n') .filter(function (line) { return line.length > 0 && line.indexOf('#') !== 0 }) .map(function (line) { return line.trim().split(':') }) } function parseDatabases(info) { return info .filter(takeFirst(startWith('db'))) .map(function _parseDatabaseInfo(args) { var dbName = args[0] var value = args[1] var values = orEmptyStr(value).split(',') // console.log({values}) function extract(param) { return parseInt(orEmptyStr(find(values, startWith(param))).split('=')[1] || 0, 10) } return { index: parseInt(dbName.substr(2), 10), keys: extract('keys'), expires: extract('expires'), avg_ttl: extract('avg_ttl'), } }) .reduce(function (m, v) { m[v.index] = { keys: v.keys, expires: v.expires, avg_ttl: v.avg_ttl, } return m }, {}) } function parseCommands(info) { return fromPairs(info.filter(function (a) { return orEmptyStr(a[0]).indexOf('cmdstat_') === 0 }) .map(function _parseCommands(args) { var v = args[0] var a = args[1] var val = fromPairs(orEmptyStr(a).split(',').map(split('='))) if (has(val, 'calls')) { val.calls = parseInt(val.calls, 10) } if (has(val, 'usec')) { val.usec = parseInt(val.usec, 10) } if (has(val, 'usec_per_call')) { val.usec_per_call = parseFloat(val.usec_per_call, 10) } return [orEmptyStr(v).split('_')[1], val] })) } function parseFields(info) { var fields = info.reduce(function (m, v) { if (!v[0].trim() || v[0].indexOf('db') === 0 || v[0].indexOf('cmdstat_') === 0) { return m } m[v[0]] = v[1] return m }, { databases: parseDatabases(info), commands: parseCommands(info) }) return fields } src/lib/ioredis-cluster/redis.mjs000066400000000000000000000020311517716222300173530ustar00rootroot00000000000000import IORedis from 'ioredis' import redisInfo from './redis-info.mjs' import Cluster from './cluster.mjs' import createWithClusterAutoDetect from './create-with-cluster-auto-detect.mjs' import getInfo from './get-info.mjs' import getClusterNodes from './get-cluster-nodes.mjs' import isClusterEnabled from './is-cluster-enabled.mjs' class Redis extends IORedis { constructor(server, {autoDetectCluster, ...options} = {}) { if (autoDetectCluster && !Array.isArray(server)) { return createWithClusterAutoDetect(server, options) } if (Array.isArray(server)) { return new Cluster(server, options) } super(server) } /* async infoObject(...args) { const info = await this.info(...args) return redisInfo.parse(info) } */ } Object.defineProperty(Redis, 'Cluster', { value: Cluster, writable: true, configurable: true }) Redis.isClusterEnabled = isClusterEnabled Redis.getClusterNodes = getClusterNodes Redis.getInfo = getInfo export default Redis src/lib/ioredis-cluster/set-default-options-from-server.mjs000066400000000000000000000025561517716222300244340ustar00rootroot00000000000000function getDefaultOptionsFromServer(server) { const server1 = Array.isArray(server) ? server[0] : server if (typeof server1 === 'object' && server1 !== null) { return server1 } } export default function(options, server) { const serverOptions = getDefaultOptionsFromServer(server) //console.log('serverOptions', serverOptions) let redisOptions = options.redisOptions if (redisOptions === undefined) { redisOptions = {} options.redisOptions = redisOptions } if (redisOptions.password === undefined) { redisOptions.password = serverOptions.password } if (serverOptions.tlsWithoutCert) { redisOptions.tls = { servername: serverOptions.host } } else if (typeof serverOptions.tlsCa === 'string' && serverOptions.tlsCa.trim() !== '') { redisOptions.tls = { cert: serverOptions.tlsCrt, key: serverOptions.tlsKey, ca: serverOptions.tlsCa, servername: serverOptions.host } } if (redisOptions.hasOwnProperty('tls')) { redisOptions.tls.rejectUnauthorized = redisOptions.tlsRejectUnauthorized === undefined ? false : redisOptions.tlsRejectUnauthorized if (!redisOptions.tls.hasOwnProperty('servername')) { redisOptions.tls.servername = serverOptions.host } } return options } src/lib/redis-command-hints.mjs000066400000000000000000000420651517716222300170100ustar00rootroot00000000000000/** * Static argument syntax hints for common Redis commands. * Format: command name (uppercase) β†’ syntax string */ export default { // Strings SET: 'key value [EX seconds | PX ms | EXAT unix-sec | PXAT unix-ms | KEEPTTL] [NX | XX] [GET]', GET: 'key', MSET: 'key value [key value ...]', MGET: 'key [key ...]', SETNX: 'key value', SETEX: 'key seconds value', PSETEX: 'key milliseconds value', GETSET: 'key value', GETDEL: 'key', GETEX: 'key [EX seconds | PX ms | EXAT unix-sec | PXAT unix-ms | PERSIST]', GETRANGE: 'key start end', SETRANGE: 'key offset value', APPEND: 'key value', STRLEN: 'key', INCR: 'key', INCRBY: 'key increment', INCRBYFLOAT: 'key increment', DECR: 'key', DECRBY: 'key decrement', MSETNX: 'key value [key value ...]', // Redis 8.4 MSETEX: 'key value [key value ...] [EX seconds | PX ms | EXAT unix-sec | PXAT unix-ms]', LCS: 'key1 key2 [LEN] [IDX] [MINMATCHLEN min] [WITHMATCHLEN]', SUBSTR: 'key start end', // Hash HSET: 'key field value [field value ...]', HGET: 'key field', HMSET: 'key field value [field value ...]', HMGET: 'key field [field ...]', HGETALL: 'key', HDEL: 'key field [field ...]', HEXISTS: 'key field', HINCRBY: 'key field increment', HINCRBYFLOAT: 'key field increment', HKEYS: 'key', HVALS: 'key', HLEN: 'key', HSETNX: 'key field value', HRANDFIELD: 'key [count [WITHVALUES]]', HSCAN: 'key cursor [MATCH pattern] [COUNT count]', // Redis 8.0 β€” per-field hash TTL HGETEX: 'key FIELDS count field [field ...] [EX seconds | PX ms | EXAT unix-sec | PXAT unix-ms | PERSIST]', HSETEX: 'key FIELDS count field value [field value ...] [EX seconds | PX ms | EXAT unix-sec | PXAT unix-ms | KEEPTTL]', HGETDEL: 'key FIELDS count field [field ...]', HTTL: 'key FIELDS count field [field ...]', HPTTL: 'key FIELDS count field [field ...]', HEXPIRE: 'key seconds FIELDS count field [field ...]', HPEXPIRE: 'key milliseconds FIELDS count field [field ...]', HEXPIREAT: 'key unix-time-seconds FIELDS count field [field ...]', HPEXPIREAT: 'key unix-time-ms FIELDS count field [field ...]', HPERSIST: 'key FIELDS count field [field ...]', HEXPIRETIME: 'key FIELDS count field [field ...]', HPEXPIRETIME: 'key FIELDS count field [field ...]', // List LPUSH: 'key element [element ...]', RPUSH: 'key element [element ...]', LPOP: 'key [count]', RPOP: 'key [count]', LLEN: 'key', LRANGE: 'key start stop', LINDEX: 'key index', LSET: 'key index element', LINSERT: 'key BEFORE|AFTER pivot element', LREM: 'key count element', LTRIM: 'key start stop', LPOS: 'key element [RANK rank] [COUNT count] [MAXLEN len]', LMOVE: 'source destination LEFT|RIGHT LEFT|RIGHT', BLPOP: 'key [key ...] timeout', BRPOP: 'key [key ...] timeout', BLMOVE: 'source destination LEFT|RIGHT LEFT|RIGHT timeout', RPOPLPUSH: 'source destination', BRPOPLPUSH: 'source destination timeout', LPUSHX: 'key element [element ...]', RPUSHX: 'key element [element ...]', LMPOP: 'numkeys key [key ...] LEFT|RIGHT [COUNT count]', BLMPOP: 'timeout numkeys key [key ...] LEFT|RIGHT [COUNT count]', // Set SADD: 'key member [member ...]', SREM: 'key member [member ...]', SMEMBERS: 'key', SISMEMBER: 'key member', SMISMEMBER: 'key member [member ...]', SCARD: 'key', SPOP: 'key [count]', SRANDMEMBER: 'key [count]', SINTER: 'key [key ...]', SUNION: 'key [key ...]', SDIFF: 'key [key ...]', SINTERSTORE: 'destination key [key ...]', SUNIONSTORE: 'destination key [key ...]', SDIFFSTORE: 'destination key [key ...]', SMOVE: 'source destination member', SINTERCARD: 'numkeys key [key ...] [LIMIT limit]', SSCAN: 'key cursor [MATCH pattern] [COUNT count]', // Sorted Set ZADD: 'key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]', ZREM: 'key member [member ...]', ZSCORE: 'key member', ZMSCORE: 'key member [member ...]', ZRANK: 'key member [WITHSCORE]', ZREVRANK: 'key member [WITHSCORE]', ZRANGE: 'key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]', ZRANGEBYSCORE: 'key min max [WITHSCORES] [LIMIT offset count]', ZREVRANGEBYSCORE: 'key max min [WITHSCORES] [LIMIT offset count]', ZRANGEBYLEX: 'key min max [LIMIT offset count]', ZREVRANGEBYLEX: 'key max min [LIMIT offset count]', ZRANGESTORE: 'dst src min max [BYSCORE|BYLEX] [REV] [LIMIT offset count]', ZCARD: 'key', ZCOUNT: 'key min max', ZLEXCOUNT: 'key min max', ZINCRBY: 'key increment member', ZINTERSTORE: 'destination numkeys key [key ...] [WEIGHTS weight ...] [AGGREGATE SUM|MIN|MAX]', ZUNIONSTORE: 'destination numkeys key [key ...] [WEIGHTS weight ...] [AGGREGATE SUM|MIN|MAX]', ZDIFFSTORE: 'destination numkeys key [key ...]', ZINTER: 'numkeys key [key ...] [WEIGHTS weight ...] [AGGREGATE SUM|MIN|MAX] [WITHSCORES]', ZUNION: 'numkeys key [key ...] [WEIGHTS weight ...] [AGGREGATE SUM|MIN|MAX] [WITHSCORES]', ZDIFF: 'numkeys key [key ...] [WITHSCORES]', ZRANDMEMBER: 'key [count [WITHSCORES]]', ZPOPMIN: 'key [count]', ZPOPMAX: 'key [count]', BZPOPMIN: 'key [key ...] timeout', BZPOPMAX: 'key [key ...] timeout', ZMPOP: 'numkeys key [key ...] MIN|MAX [COUNT count]', BZMPOP: 'timeout numkeys key [key ...] MIN|MAX [COUNT count]', ZSCAN: 'key cursor [MATCH pattern] [COUNT count]', // Key DEL: 'key [key ...]', UNLINK: 'key [key ...]', EXISTS: 'key [key ...]', EXPIRE: 'key seconds [NX|XX|GT|LT]', PEXPIRE: 'key milliseconds [NX|XX|GT|LT]', EXPIREAT: 'key unix-time-seconds [NX|XX|GT|LT]', PEXPIREAT: 'key unix-time-milliseconds [NX|XX|GT|LT]', PERSIST: 'key', TTL: 'key', PTTL: 'key', EXPIRETIME: 'key', PEXPIRETIME: 'key', TYPE: 'key', RENAME: 'key newkey', RENAMENX: 'key newkey', KEYS: 'pattern', SCAN: 'cursor [MATCH pattern] [COUNT count] [TYPE type]', RANDOMKEY: '', DUMP: 'key', RESTORE: 'key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]', COPY: 'source destination [DB destination-db] [REPLACE]', MOVE: 'key db', OBJECT: 'ENCODING|FREQ|HELP|IDLETIME|REFCOUNT key', SORT: 'key [BY pattern] [LIMIT offset count] [GET pattern ...] [ASC|DESC] [ALPHA] [STORE destination]', SORT_RO: 'key [BY pattern] [LIMIT offset count] [GET pattern ...] [ASC|DESC] [ALPHA]', TOUCH: 'key [key ...]', WAIT: 'numreplicas timeout', WAITAOF: 'numlocal numreplicas timeout', // Stream XADD: 'key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|id field value [field value ...]', XREAD: '[COUNT count] [BLOCK ms] STREAMS key [key ...] id [id ...]', XREADGROUP: 'GROUP group consumer [COUNT count] [BLOCK ms] [NOACK] STREAMS key [key ...] id [id ...]', XRANGE: 'key start end [COUNT count]', XREVRANGE: 'key end start [COUNT count]', XLEN: 'key', XINFO: 'CONSUMERS|GROUPS|STREAM key [FULL [COUNT count]]', XACK: 'key group id [id ...]', XCLAIM: 'key group consumer min-idle-time id [id ...] [IDLE ms] [TIME ms] [RETRYCOUNT count] [FORCE] [JUSTID] [LASTID id]', XAUTOCLAIM: 'key group consumer min-idle-time start [COUNT count] [JUSTID]', XDEL: 'key id [id ...]', XTRIM: 'key MAXLEN|MINID [=|~] threshold [LIMIT count]', XGROUP: 'CREATE|CREATECONSUMER|DELCONSUMER|DESTROY|SETID key group [id|$] [MKSTREAM] [ENTRIESREAD entries-read]', XPENDING: 'key group [[IDLE min-idle-time] start end count [consumer]]', XSETID: 'key last-id [ENTRIESREAD entries-read]', // Redis 8.2 XDELEX: 'key id [id ...] [GROUP group] [IDLE min-idle-time]', // Redis 8.6 XCFGSET: 'key parameter value', // Server INFO: '[section ...]', DBSIZE: '', FLUSHDB: '[ASYNC|SYNC]', FLUSHALL: '[ASYNC|SYNC]', SELECT: 'index', PING: '[message]', ECHO: 'message', QUIT: '', CONFIG: 'GET|SET|RESETSTAT|REWRITE parameter [value]', SLOWLOG: 'GET|LEN|RESET [count]', CLIENT: 'GETNAME|ID|INFO|KILL|LIST|NO-EVICT|PAUSE|REPLY|SETNAME|UNPAUSE|NO-TOUCH ...', MEMORY: 'DOCTOR|HELP|MALLOC-STATS|PURGE|STATS|USAGE key [SAMPLES count]', TIME: '', LASTSAVE: '', BGSAVE: '[SCHEDULE]', BGREWRITEAOF: '', SAVE: '', SHUTDOWN: '[NOSAVE|SAVE] [NOW] [FORCE]', ACL: 'CAT|DELUSER|GENPASS|GETUSER|LIST|LOAD|LOG|SAVE|SETUSER|WHOAMI ...', MODULE: 'LIST|LOAD|LOADEX|UNLOAD ...', COMMAND: '[COUNT|DOCS|GETKEYS|INFO|LIST ...]', DEBUG: 'subcommand [arg ...]', LATENCY: 'HISTORY|LATEST|RESET event', SWAPDB: 'index1 index2', REPLICAOF: 'host port', SLAVEOF: 'host port', FAILOVER: '[TO host port [FORCE]] [ABORT] [TIMEOUT ms]', CLUSTER: 'INFO|NODES|SLOTS|MYID|RESET|KEYSLOT|SLOT-STATS|MIGRATION|...', // Redis 8.4 DIGEST: 'key', // Pub/Sub PUBLISH: 'channel message', SUBSCRIBE: 'channel [channel ...]', UNSUBSCRIBE: '[channel ...]', PSUBSCRIBE: 'pattern [pattern ...]', PUNSUBSCRIBE: '[pattern ...]', PUBSUB: 'CHANNELS|NUMSUB|NUMPAT|SHARDCHANNELS|SHARDNUMSUB [argument ...]', // Scripting EVAL: '"script" numkeys [key ...] [arg ...]', EVALSHA: 'sha1 numkeys [key ...] [arg ...]', EVALRO: '"script" numkeys [key ...] [arg ...]', EVALSHA_RO: 'sha1 numkeys [key ...] [arg ...]', SCRIPT: 'DEBUG|EXISTS|FLUSH|LOAD ...', FCALL: 'function numkeys [key ...] [arg ...]', FCALL_RO: 'function numkeys [key ...] [arg ...]', FUNCTION: 'CREATE|DELETE|DUMP|FLUSH|LIST|LOAD|RESTORE|STATS ...', // Geo GEOADD: 'key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]', GEOPOS: 'key member [member ...]', GEODIST: 'key member1 member2 [M|KM|FT|MI]', GEOSEARCH: 'key FROMMEMBER member|FROMLONLAT lng lat BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]', GEOSEARCHSTORE: 'destination source FROMMEMBER member|FROMLONLAT lng lat BYRADIUS radius unit|BYBOX width height unit [ASC|DESC] [COUNT count [ANY]] [STOREDIST]', GEORADIUS: 'key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]', GEORADIUSBYMEMBER: 'key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]', GEOHASH: 'key member [member ...]', // HyperLogLog PFADD: 'key element [element ...]', PFCOUNT: 'key [key ...]', PFMERGE: 'destkey sourcekey [sourcekey ...]', // Bitmap SETBIT: 'key offset value', GETBIT: 'key offset', BITCOUNT: 'key [start end [BYTE|BIT]]', BITPOS: 'key bit [start [end [BYTE|BIT]]]', BITOP: 'AND|OR|XOR|NOT|DIFF|DIFF1|ANDOR|ONE destkey key [key ...]', BITFIELD: 'key [GET encoding offset | SET encoding offset value | INCRBY encoding offset increment | OVERFLOW WRAP|SAT|FAIL] ...', BITFIELD_RO: 'key GET encoding offset [GET encoding offset ...]', // JSON (ReJSON) 'JSON.SET': 'key path value [NX|XX]', 'JSON.GET': 'key [path ...]', 'JSON.DEL': 'key [path]', 'JSON.MGET': 'key [key ...] path', 'JSON.TYPE': 'key [path]', 'JSON.NUMINCRBY': 'key path value', 'JSON.NUMMULTBY': 'key path value', 'JSON.STRAPPEND': 'key [path] value', 'JSON.STRLEN': 'key [path]', 'JSON.ARRAPPEND': 'key path value [value ...]', 'JSON.ARRINDEX': 'key path value [start [stop]]', 'JSON.ARRINSERT': 'key path index value [value ...]', 'JSON.ARRLEN': 'key [path]', 'JSON.ARRPOP': 'key [path [index]]', 'JSON.ARRTRIM': 'key path start stop', 'JSON.OBJKEYS': 'key [path]', 'JSON.OBJLEN': 'key [path]', 'JSON.RESP': 'key [path]', // RediSearch 'FT.CREATE': 'index [ON HASH|JSON] [PREFIX count prefix ...] SCHEMA field TYPE [SORTABLE] ...', 'FT.SEARCH': 'index query [NOCONTENT] [VERBATIM] [NOSTOPWORDS] [WITHSCORES] [WITHPAYLOADS] [WITHSORTKEYS] [FILTER field min max ...] [GEOFILTER field lon lat radius unit] [INKEYS count key ...] [INFIELDS count field ...] [RETURN count field ...] [SUMMARIZE ...] [HIGHLIGHT ...] [SLOP slop] [TIMEOUT timeout] [INORDER] [LANGUAGE lang] [EXPANDER exp] [SCORER scorer] [SORTBY field [ASC|DESC]] [LIMIT offset num]', 'FT.AGGREGATE': 'index query [LOAD count field ...] [GROUPBY count field ... REDUCE func nargs arg ... [AS name]] [SORTBY count field [ASC|DESC] ...] [APPLY expr AS name] [LIMIT offset num] [FILTER expr]', 'FT.INFO': 'index', 'FT.DROPINDEX': 'index [DD]', 'FT._LIST': '', 'FT.ALTER': 'index SCHEMA ADD field TYPE ...', 'FT.ALIASADD': 'alias index', 'FT.ALIASDEL': 'alias', 'FT.ALIASUPDATE': 'alias index', 'FT.TAGVALS': 'index field', 'FT.EXPLAIN': 'index query', // Redis 8.4 'FT.HYBRID': 'index query [SCORER scorer] [WEIGHT text_weight vector_weight] [LIMIT offset num] VECTOR field num value [EF_RUNTIME efrt] [EPSILON eps]', // TimeSeries 'TS.CREATE': 'key [RETENTION retentionPeriod] [ENCODING UNCOMPRESSED|COMPRESSED] [CHUNK_SIZE size] [DUPLICATE_POLICY policy] [LABELS label value ...]', 'TS.ADD': 'key timestamp value [RETENTION retentionPeriod] [ENCODING UNCOMPRESSED|COMPRESSED] [CHUNK_SIZE size] [ON_DUPLICATE policy] [LABELS label value ...]', 'TS.MADD': 'key timestamp value [key timestamp value ...]', 'TS.INCRBY': 'key value [TIMESTAMP timestamp] [RETENTION retentionPeriod] [LABELS label value ...]', 'TS.DECRBY': 'key value [TIMESTAMP timestamp] [RETENTION retentionPeriod] [LABELS label value ...]', 'TS.DEL': 'key fromTimestamp toTimestamp', 'TS.RANGE': 'key fromTimestamp toTimestamp [LATEST] [FILTER_BY_TS ts ...] [FILTER_BY_VALUE min max] [COUNT count] [AGGREGATION aggregator bucketDuration]', 'TS.REVRANGE': 'key fromTimestamp toTimestamp [LATEST] [FILTER_BY_TS ts ...] [FILTER_BY_VALUE min max] [COUNT count] [AGGREGATION aggregator bucketDuration]', 'TS.MRANGE': 'fromTimestamp toTimestamp [LATEST] [FILTER_BY_TS ts ...] [FILTER_BY_VALUE min max] [WITHLABELS] [COUNT count] [AGGREGATION aggregator bucketDuration] FILTER filter ...', 'TS.MREVRANGE': 'fromTimestamp toTimestamp [LATEST] [FILTER_BY_TS ts ...] [FILTER_BY_VALUE min max] [WITHLABELS] [COUNT count] [AGGREGATION aggregator bucketDuration] FILTER filter ...', 'TS.GET': 'key [LATEST]', 'TS.MGET': '[LATEST] [WITHLABELS] FILTER filter ...', 'TS.INFO': 'key [DEBUG]', 'TS.ALTER': 'key [RETENTION retentionPeriod] [CHUNK_SIZE size] [DUPLICATE_POLICY policy] [LABELS label value ...]', 'TS.QUERYINDEX': 'filter ...', 'TS.CREATERULE': 'sourceKey destKey AGGREGATION aggregator bucketDuration [alignTimestamp]', 'TS.DELETERULE': 'sourceKey destKey', // Bloom Filter 'BF.RESERVE': 'key error_rate capacity [EXPANSION expansion] [NONSCALING]', 'BF.ADD': 'key item', 'BF.MADD': 'key item [item ...]', 'BF.EXISTS': 'key item', 'BF.MEXISTS': 'key item [item ...]', 'BF.INFO': 'key [CAPACITY|SIZE|FILTERS|ITEMS|EXPANSION]', 'BF.INSERT': 'key [CAPACITY cap] [ERROR error] [EXPANSION exp] [NOCREATE] [NONSCALING] ITEMS item [item ...]', // Cuckoo Filter 'CF.RESERVE': 'key capacity [BUCKETSIZE bucketsize] [MAXITERATIONS maxiterations] [EXPANSION expansion]', 'CF.ADD': 'key item', 'CF.ADDNX': 'key item', 'CF.EXISTS': 'key item', 'CF.MEXISTS': 'key item [item ...]', 'CF.DEL': 'key item', 'CF.COUNT': 'key item', 'CF.INFO': 'key', // Top-K 'TOPK.RESERVE': 'key topk [width] [depth] [decay]', 'TOPK.ADD': 'key item [item ...]', 'TOPK.INCRBY': 'key item increment [item increment ...]', 'TOPK.QUERY': 'key item [item ...]', 'TOPK.COUNT': 'key item [item ...]', 'TOPK.LIST': 'key [WITHCOUNT]', 'TOPK.INFO': 'key', // Count-Min Sketch 'CMS.INITBYDIM': 'key width depth', 'CMS.INITBYPROB': 'key error probability', 'CMS.INCRBY': 'key item increment [item increment ...]', 'CMS.QUERY': 'key item [item ...]', 'CMS.MERGE': 'destination numkeys source [source ...] [WEIGHTS weight [weight ...]]', 'CMS.INFO': 'key', // T-Digest 'TDIGEST.CREATE': 'key [COMPRESSION compression]', 'TDIGEST.ADD': 'key value [value ...]', 'TDIGEST.MERGE': 'destination-key numkeys source-key [source-key ...] [COMPRESSION compression] [OVERRIDE]', 'TDIGEST.CDF': 'key value [value ...]', 'TDIGEST.QUANTILE': 'key quantile [quantile ...]', 'TDIGEST.MIN': 'key', 'TDIGEST.MAX': 'key', 'TDIGEST.TRIMMED_MEAN': 'key low_cut_quantile high_cut_quantile', 'TDIGEST.RANK': 'key value [value ...]', 'TDIGEST.REVRANK': 'key value [value ...]', 'TDIGEST.BYRANK': 'key rank [rank ...]', 'TDIGEST.BYREVRANK': 'key rank [rank ...]', 'TDIGEST.INFO': 'key', 'TDIGEST.RESET': 'key', // VectorSet (Redis 8) 'VADD': 'key [REDUCE dim] VALUES dim val [val ...] element [SETATTR "field\\nvalue\\n..."] [CAS]', 'VSIM': 'key {ELE element | VALUES dim val [val ...]} [COUNT count] [WITHSCORES] [FILTER expr]', 'VCARD': 'key', 'VDIM': 'key', 'VGETATTR': 'key element', 'VSETATTR': 'key element "field\\nvalue\\n..."', 'VREM': 'key element', 'VINFO': 'key', 'VLINKS': 'key element [WITHSCORES]', 'VRANDMEMBER': 'key [count]', 'VEMB': 'key element', } src/lib/redis-command-meta.mjs000066400000000000000000000047471517716222300166160ustar00rootroot00000000000000import commandHints from './redis-command-hints.mjs' /** * Category display names and order for autocomplete grouping. */ const categoryMap = { '@string': 'String', '@hash': 'Hash', '@list': 'List', '@set': 'Set', '@sortedset': 'Sorted Set', '@stream': 'Stream', '@geo': 'Geo', '@hyperloglog': 'HyperLogLog', '@bitmap': 'Bitmap', '@keyspace': 'Key', '@connection': 'Connection', '@server': 'Server', '@generic': 'Generic', '@pubsub': 'Pub/Sub', '@scripting': 'Scripting', '@transactions': 'Transactions', '@cluster': 'Cluster', '@slow': null, // skip β€” too generic '@fast': null, '@read': null, '@write': null, '@dangerous': null, '@admin': null, } /** * Process raw redis.command() output into metadata for autocomplete. * Returns: { [commandName]: { syntax, group } } */ export function buildCommandMeta(rawCommands) { const meta = {} if (Array.isArray(rawCommands)) { // Static commands format: array of arrays for (const entry of rawCommands) { processEntry(entry, meta) } } else if (rawCommands && typeof rawCommands === 'object') { // Live redis.command() format: object keyed by command name for (const key of Object.keys(rawCommands)) { processEntry(rawCommands[key], meta) } } return meta } function processEntry(entry, meta) { if (!Array.isArray(entry) || entry.length < 7) return const name = entry[0]?.toUpperCase() if (!name) return const categories = entry[6] || [] // Find the best display category let group = 'Other' for (const cat of categories) { if (categoryMap.hasOwnProperty(cat) && categoryMap[cat] !== null) { group = categoryMap[cat] break } } // Static syntax hint or empty const syntax = commandHints[name] ?? '' meta[name] = { syntax, group } // Process subcommands (index 9) const subcommands = entry[9] if (Array.isArray(subcommands)) { for (const sub of subcommands) { if (!Array.isArray(sub) || sub.length < 7) continue const subName = sub[0]?.toUpperCase() if (!subName) continue // Subcommand names come as "config|get" β€” convert to "CONFIG GET" const displayName = subName.replace('|', ' ') const subSyntax = commandHints[displayName] ?? '' meta[displayName] = { syntax: subSyntax, group } } } } src/lib/redis-static-commands.mjs000066400000000000000000002053261517716222300173360ustar00rootroot00000000000000export default [["zrandmember",-2,["readonly"],1,1,1,["@read","@sortedset","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["geosearch",-7,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["auth",-2,["noscript","loading","stale","fast","no_auth","allow_busy"],0,0,0,["@fast","@connection"],[],[],[]],["touch",-2,["readonly","fast"],1,-1,1,["@keyspace","@read","@fast"],["request_policy:multi_shard","response_policy:agg_sum"],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["dump",2,["readonly"],1,1,1,["@keyspace","@read","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["config",-2,[],0,0,0,["@slow"],[],[],[["config|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["config|rewrite",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["config|get",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["config|resetstat",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["config|set",-4,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["request_policy:all_nodes","response_policy:all_succeeded"],[],[]]]],["pttl",2,["readonly","fast"],1,1,1,["@keyspace","@read","@fast"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["set",-3,["write","denyoom"],1,1,1,["@write","@string","@slow"],[],[["notes","RW and ACCESS due to the optional `GET` argument","flags",["RW","access","update","variable_flags"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["smove",4,["write","fast"],1,2,1,["@write","@set","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["randomkey",1,["readonly"],0,0,0,["@keyspace","@read","@slow"],["request_policy:all_shards","nondeterministic_output"],[],[]],["getdel",2,["write","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xinfo",-2,[],0,0,0,["@slow"],[],[],[["xinfo|help",2,["loading","stale"],0,0,0,["@stream","@slow"],[],[],[]],["xinfo|stream",-3,["readonly"],2,2,1,["@read","@stream","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xinfo|consumers",4,["readonly"],2,2,1,["@read","@stream","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xinfo|groups",3,["readonly"],2,2,1,["@read","@stream","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]]]],["replicaof",3,["admin","noscript","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["wait",3,["noscript"],0,0,0,["@slow","@connection"],["request_policy:all_shards","response_policy:agg_min"],[],[]],["move",3,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["bzmpop",-5,["write","blocking","movablekeys"],0,0,0,["@write","@sortedset","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["lpos",-3,["readonly"],1,1,1,["@read","@list","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["module",-2,[],0,0,0,["@slow"],[],[],[["module|loadex",-3,["admin","noscript","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["module|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["module|load",-3,["admin","noscript","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["module|list",2,["admin","noscript"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output_order"],[],[]],["module|unload",3,["admin","noscript","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]]]],["zrevrangebyscore",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zremrangebyscore",4,["write"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hrandfield",-2,["readonly"],1,1,1,["@read","@hash","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pfdebug",3,["write","denyoom","admin"],2,2,1,["@write","@hyperloglog","@admin","@slow","@dangerous"],[],[["flags",["RW","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["migrate",-6,["write","movablekeys"],3,3,1,["@keyspace","@write","@slow","@dangerous"],["nondeterministic_output"],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",3]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","access","delete","incomplete"],"begin_search",["type","keyword","spec",["keyword","KEYS","startfrom",-2]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["xack",-4,["write","fast"],1,1,1,["@write","@stream","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["flushall",-1,["write"],0,0,0,["@keyspace","@write","@slow","@dangerous"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["multi",1,["noscript","loading","stale","fast","allow_busy"],0,0,0,["@fast","@transaction"],[],[],[]],["renamenx",3,["write","fast"],1,2,1,["@keyspace","@write","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pexpiretime",2,["readonly","fast"],1,1,1,["@keyspace","@read","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sync",1,["admin","noscript","no_async_loading","no_multi"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["lpush",-3,["write","denyoom","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["strlen",2,["readonly","fast"],1,1,1,["@read","@string","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zinter",-3,["readonly","movablekeys"],0,0,0,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["geoadd",-5,["write","denyoom"],1,1,1,["@write","@geo","@slow"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xadd",-5,["write","denyoom","fast"],1,1,1,["@write","@stream","@fast"],["nondeterministic_output"],[["notes","UPDATE instead of INSERT because of the optional trimming feature","flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lolwut",-1,["readonly","fast"],0,0,0,["@read","@fast"],[],[],[]],["xlen",2,["readonly","fast"],1,1,1,["@read","@stream","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zinterstore",-4,["write","denyoom","movablekeys"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["unwatch",1,["noscript","loading","stale","fast","allow_busy"],0,0,0,["@fast","@transaction"],[],[],[]],["xtrim",-4,["write"],1,1,1,["@write","@stream","@slow"],["nondeterministic_output"],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hdel",-3,["write","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xsetid",-3,["write","denyoom","fast"],1,1,1,["@write","@stream","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["reset",1,["noscript","loading","stale","fast","no_auth","allow_busy"],0,0,0,["@fast","@connection"],[],[],[]],["scan",-2,["readonly"],0,0,0,["@keyspace","@read","@slow"],["nondeterministic_output","request_policy:special"],[],[]],["del",-2,["write"],1,-1,1,["@keyspace","@write","@slow"],["request_policy:multi_shard","response_policy:agg_sum"],[["flags",["RM","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["zlexcount",4,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zdiff",-3,["readonly","movablekeys"],0,0,0,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["sinterstore",-3,["write","denyoom"],1,-1,1,["@write","@set","@slow"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["zincrby",4,["write","denyoom","fast"],1,1,1,["@write","@sortedset","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["georadiusbymember",-5,["write","denyoom","movablekeys"],1,1,1,["@write","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","keyword","spec",["keyword","STORE","startfrom",5]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","keyword","spec",["keyword","STOREDIST","startfrom",5]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["rpushx",-3,["write","denyoom","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["swapdb",3,["write","fast"],0,0,0,["@keyspace","@write","@fast","@dangerous"],[],[],[]],["command",-1,["loading","stale"],0,0,0,["@slow","@connection"],["nondeterministic_output_order"],[],[["command|getkeys",-4,["loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["command|docs",-2,["loading","stale"],0,0,0,["@slow","@connection"],["nondeterministic_output_order"],[],[]],["command|info",-2,["loading","stale"],0,0,0,["@slow","@connection"],["nondeterministic_output_order"],[],[]],["command|help",2,["loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["command|count",2,["loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["command|list",-2,["loading","stale"],0,0,0,["@slow","@connection"],["nondeterministic_output_order"],[],[]],["command|getkeysandflags",-4,["loading","stale"],0,0,0,["@slow","@connection"],[],[],[]]]],["function",-2,[],0,0,0,["@slow"],[],[],[["function|delete",3,["write","noscript"],0,0,0,["@write","@slow","@scripting"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["function|help",2,["loading","stale"],0,0,0,["@slow","@scripting"],[],[],[]],["function|load",-3,["write","denyoom","noscript"],0,0,0,["@write","@slow","@scripting"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["function|dump",2,["noscript"],0,0,0,["@slow","@scripting"],[],[],[]],["function|flush",-2,["write","noscript"],0,0,0,["@write","@slow","@scripting"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["function|list",-2,["noscript"],0,0,0,["@slow","@scripting"],["nondeterministic_output_order"],[],[]],["function|kill",2,["noscript","allow_busy"],0,0,0,["@slow","@scripting"],["request_policy:all_shards","response_policy:one_succeeded"],[],[]],["function|restore",-3,["write","denyoom","noscript"],0,0,0,["@write","@slow","@scripting"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["function|stats",2,["noscript","allow_busy"],0,0,0,["@slow","@scripting"],["nondeterministic_output","request_policy:all_shards","response_policy:special"],[],[]]]],["script",-2,[],0,0,0,["@slow"],[],[],[["script|exists",-3,["noscript"],0,0,0,["@slow","@scripting"],["request_policy:all_shards","response_policy:agg_logical_and"],[],[]],["script|flush",-2,["noscript"],0,0,0,["@slow","@scripting"],["request_policy:all_nodes","response_policy:all_succeeded"],[],[]],["script|debug",3,["noscript"],0,0,0,["@slow","@scripting"],[],[],[]],["script|help",2,["loading","stale"],0,0,0,["@slow","@scripting"],[],[],[]],["script|load",3,["noscript","stale"],0,0,0,["@slow","@scripting"],["request_policy:all_nodes","response_policy:all_succeeded"],[],[]],["script|kill",2,["noscript","allow_busy"],0,0,0,["@slow","@scripting"],["request_policy:all_shards","response_policy:one_succeeded"],[],[]]]],["sadd",-3,["write","denyoom","fast"],1,1,1,["@write","@set","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["subscribe",-2,["pubsub","noscript","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["srem",-3,["write","fast"],1,1,1,["@write","@set","@fast"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hlen",2,["readonly","fast"],1,1,1,["@read","@hash","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hstrlen",3,["readonly","fast"],1,1,1,["@read","@hash","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["exists",-2,["readonly","fast"],1,-1,1,["@keyspace","@read","@fast"],["request_policy:multi_shard","response_policy:agg_sum"],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["zadd",-4,["write","denyoom","fast"],1,1,1,["@write","@sortedset","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["substr",4,["readonly"],1,1,1,["@read","@string","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["restore",-4,["write","denyoom"],1,1,1,["@keyspace","@write","@slow","@dangerous"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["fcall",-3,["noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["notes","We cannot tell how the keys will be used so we assume the worst, RW and UPDATE","flags",["RW","access","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["lcs",-3,["readonly"],1,2,1,["@read","@string","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",1,"keystep",1,"limit",0]]]],[]],["bgrewriteaof",1,["admin","noscript","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["hgetall",2,["readonly"],1,1,1,["@read","@hash","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["debug",-2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["spublish",3,["pubsub","loading","stale","fast"],1,1,1,["@pubsub","@fast"],[],[["flags",["not_key"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["setex",4,["write","denyoom"],1,1,1,["@write","@string","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["expireat",-3,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["incr",2,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["rpush",-3,["write","denyoom","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["get",2,["readonly","fast"],1,1,1,["@read","@string","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zrange",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sunion",-2,["readonly"],1,-1,1,["@read","@set","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["geosearchstore",-8,["write","denyoom"],1,2,1,["@write","@geo","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sismember",3,["readonly","fast"],1,1,1,["@read","@set","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pexpire",-3,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["linsert",5,["write","denyoom"],1,1,1,["@write","@list","@slow"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["geopos",-2,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["quit",-1,["noscript","loading","stale","fast","no_auth","allow_busy"],0,0,0,["@fast","@connection"],[],[],[]],["pfselftest",1,["admin"],0,0,0,["@hyperloglog","@admin","@slow","@dangerous"],[],[],[]],["mset",-3,["write","denyoom"],1,-1,2,["@write","@string","@slow"],["request_policy:multi_shard","response_policy:all_succeeded"],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",2,"limit",0]]]],[]],["readonly",1,["loading","stale","fast"],0,0,0,["@fast","@connection"],[],[],[]],["zcount",4,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["evalsha_ro",-3,["readonly","noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["zscore",3,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lrem",4,["write"],1,1,1,["@write","@list","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lmove",5,["write","denyoom"],1,2,1,["@write","@list","@slow"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["restore-asking",-4,["write","denyoom","asking"],1,1,1,["@keyspace","@write","@slow","@dangerous"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["client",-2,[],0,0,0,["@slow"],[],[],[["client|unpause",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],[],[],[]],["client|setname",3,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|caching",3,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|info",2,["noscript","loading","stale"],0,0,0,["@slow","@connection"],["nondeterministic_output"],[],[]],["client|getname",2,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|tracking",-3,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|id",2,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|reply",3,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|help",2,["loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|pause",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],[],[],[]],["client|no-evict",3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],[],[],[]],["client|unblock",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],[],[],[]],["client|trackinginfo",2,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]],["client|list",-2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],["nondeterministic_output"],[],[]],["client|kill",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous","@connection"],[],[],[]],["client|getredir",2,["noscript","loading","stale"],0,0,0,["@slow","@connection"],[],[],[]]]],["bitfield",-2,["write","denyoom"],1,1,1,["@write","@bitmap","@slow"],[],[["notes","This command allows both access and modification of the key","flags",["RW","access","update","variable_flags"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["dbsize",1,["readonly","fast"],0,0,0,["@keyspace","@read","@fast"],["request_policy:all_shards","response_policy:agg_sum"],[],[]],["hincrby",4,["write","denyoom","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lpushx",-3,["write","denyoom","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["decrby",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zscan",-3,["readonly"],1,1,1,["@read","@sortedset","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zpopmin",-2,["write","fast"],1,1,1,["@write","@sortedset","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lindex",3,["readonly"],1,1,1,["@read","@list","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zremrangebylex",4,["write"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["object",-2,[],0,0,0,["@slow"],[],[],[["object|help",2,["loading","stale"],0,0,0,["@keyspace","@slow"],[],[],[]],["object|encoding",3,["readonly"],2,2,1,["@keyspace","@read","@slow"],["nondeterministic_output"],[["flags",["RO"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["object|idletime",3,["readonly"],2,2,1,["@keyspace","@read","@slow"],["nondeterministic_output"],[["flags",["RO"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["object|refcount",3,["readonly"],2,2,1,["@keyspace","@read","@slow"],["nondeterministic_output"],[["flags",["RO"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["object|freq",3,["readonly"],2,2,1,["@keyspace","@read","@slow"],["nondeterministic_output"],[["flags",["RO"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]]]],["sdiff",-2,["readonly"],1,-1,1,["@read","@set","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["lmpop",-4,["write","movablekeys"],0,0,0,["@write","@list","@slow"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["zremrangebyrank",4,["write"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["asking",1,["fast"],0,0,0,["@fast","@connection"],[],[],[]],["blpop",-3,["write","noscript","blocking"],1,-2,1,["@write","@list","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-2,"keystep",1,"limit",0]]]],[]],["sort",-2,["write","denyoom","movablekeys"],1,1,1,["@write","@set","@sortedset","@list","@slow","@dangerous"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["notes","For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort","flags",["RO","access"],"begin_search",["type","unknown","spec",[]],"find_keys",["type","unknown","spec",[]]],["notes","For the optional STORE keyword. It is marked 'unknown' because the keyword can appear anywhere in the argument array","flags",["OW","update"],"begin_search",["type","unknown","spec",[]],"find_keys",["type","unknown","spec",[]]]],[]],["ping",-1,["fast"],0,0,0,["@fast","@connection"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["publish",3,["pubsub","loading","stale","fast"],0,0,0,["@pubsub","@fast"],[],[],[]],["geodist",-4,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zrem",-3,["write","fast"],1,1,1,["@write","@sortedset","@fast"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["georadius_ro",-6,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["ttl",2,["readonly","fast"],1,1,1,["@keyspace","@read","@fast"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["setrange",4,["write","denyoom"],1,1,1,["@write","@string","@slow"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["bitpos",-3,["readonly"],1,1,1,["@read","@bitmap","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zcard",2,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sunsubscribe",-1,["pubsub","noscript","loading","stale"],1,-1,1,["@pubsub","@slow"],[],[["flags",["not_key"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["zpopmax",-2,["write","fast"],1,1,1,["@write","@sortedset","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["llen",2,["readonly","fast"],1,1,1,["@read","@list","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["brpop",-3,["write","noscript","blocking"],1,-2,1,["@write","@list","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-2,"keystep",1,"limit",0]]]],[]],["lrange",4,["readonly"],1,1,1,["@read","@list","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["expire",-3,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["unsubscribe",-1,["pubsub","noscript","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["zrevrangebylex",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["spop",-2,["write","fast"],1,1,1,["@write","@set","@fast"],["nondeterministic_output"],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zunionstore",-4,["write","denyoom","movablekeys"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["incrby",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sintercard",-3,["readonly","movablekeys"],0,0,0,["@read","@set","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["xclaim",-6,["write","fast"],1,1,1,["@write","@stream","@fast"],["nondeterministic_output"],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["getrange",4,["readonly"],1,1,1,["@read","@string","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sinter",-2,["readonly"],1,-1,1,["@read","@set","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["psync",-3,["admin","noscript","no_async_loading","no_multi"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["failover",-1,["admin","noscript","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["pfcount",-2,["readonly"],1,-1,1,["@read","@hyperloglog","@slow"],[],[["notes","RW because it may change the internal representation of the key, and propagate to replicas","flags",["RW","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["hscan",-3,["readonly"],1,1,1,["@read","@hash","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zrangebylex",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["replconf",-1,["admin","noscript","loading","stale","allow_busy"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl",-2,[],0,0,0,["@slow"],[],[],[["acl|cat",-2,["noscript","loading","stale"],0,0,0,["@slow"],[],[],[]],["acl|save",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|users",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|getuser",3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|dryrun",-4,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["acl|load",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|list",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|whoami",2,["noscript","loading","stale"],0,0,0,["@slow"],[],[],[]],["acl|genpass",-2,["noscript","loading","stale"],0,0,0,["@slow"],[],[],[]],["acl|deluser",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|log",-2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["acl|setuser",-3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]]]],["eval_ro",-3,["readonly","noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["notes","We cannot tell how the keys will be used so we assume the worst, RO and ACCESS","flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["xgroup",-2,[],0,0,0,["@slow"],[],[],[["xgroup|create",-5,["write","denyoom"],2,2,1,["@write","@stream","@slow"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xgroup|delconsumer",5,["write"],2,2,1,["@write","@stream","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xgroup|createconsumer",5,["write","denyoom"],2,2,1,["@write","@stream","@slow"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xgroup|help",2,["loading","stale"],0,0,0,["@stream","@slow"],[],[],[]],["xgroup|setid",-5,["write"],2,2,1,["@write","@stream","@slow"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xgroup|destroy",4,["write"],2,2,1,["@write","@stream","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]]]],["ltrim",4,["write"],1,1,1,["@write","@list","@slow"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["psubscribe",-2,["pubsub","noscript","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["hmget",-3,["readonly","fast"],1,1,1,["@read","@hash","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["info",-1,["loading","stale"],0,0,0,["@slow","@dangerous"],["nondeterministic_output","request_policy:all_shards","response_policy:special"],[],[]],["watch",-2,["noscript","loading","stale","fast","allow_busy"],1,-1,1,["@fast","@transaction"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["slowlog",-2,[],0,0,0,["@slow"],[],[],[["slowlog|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["slowlog|get",-2,["admin","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["request_policy:all_nodes","nondeterministic_output"],[],[]],["slowlog|reset",2,["admin","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["request_policy:all_nodes","response_policy:all_succeeded"],[],[]],["slowlog|len",2,["admin","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["request_policy:all_nodes","response_policy:agg_sum","nondeterministic_output"],[],[]]]],["xautoclaim",-6,["write","fast"],1,1,1,["@write","@stream","@fast"],["nondeterministic_output"],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["bitop",-4,["write","denyoom"],2,-1,1,["@write","@bitmap","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",3]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["hvals",2,["readonly"],1,1,1,["@read","@hash","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["latency",-2,[],0,0,0,["@slow"],[],[],[["latency|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["latency|histogram",-2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output","request_policy:all_nodes","response_policy:special"],[],[]],["latency|history",3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output","request_policy:all_nodes","response_policy:special"],[],[]],["latency|graph",3,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output","request_policy:all_nodes","response_policy:special"],[],[]],["latency|reset",-2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["request_policy:all_nodes","response_policy:all_succeeded"],[],[]],["latency|doctor",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output","request_policy:all_nodes","response_policy:special"],[],[]],["latency|latest",2,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output","request_policy:all_nodes","response_policy:special"],[],[]]]],["time",1,["loading","stale","fast"],0,0,0,["@fast"],["nondeterministic_output"],[],[]],["xrange",-4,["readonly"],1,1,1,["@read","@stream","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hkeys",2,["readonly"],1,1,1,["@read","@hash","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["copy",-3,["write","denyoom"],1,2,1,["@keyspace","@write","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["punsubscribe",-1,["pubsub","noscript","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["hello",-1,["noscript","loading","stale","fast","no_auth","allow_busy"],0,0,0,["@fast","@connection"],[],[],[]],["xdel",-3,["write","fast"],1,1,1,["@write","@stream","@fast"],[],[["flags",["RW","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["blmove",6,["write","denyoom","noscript","blocking"],1,2,1,["@write","@list","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["bzpopmin",-3,["write","noscript","blocking","fast"],1,-2,1,["@write","@sortedset","@fast","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-2,"keystep",1,"limit",0]]]],[]],["flushdb",-1,["write"],0,0,0,["@keyspace","@write","@slow","@dangerous"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["hmset",-4,["write","denyoom","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sdiffstore",-3,["write","denyoom"],1,-1,1,["@write","@set","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["smismember",-3,["readonly","fast"],1,1,1,["@read","@set","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["getex",-2,["write","fast"],1,1,1,["@write","@string","@fast"],[],[["notes","RW and UPDATE because it changes the TTL","flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["bitcount",-2,["readonly"],1,1,1,["@read","@bitmap","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["discard",1,["noscript","loading","stale","fast","allow_busy"],0,0,0,["@fast","@transaction"],[],[],[]],["pfadd",-2,["write","denyoom","fast"],1,1,1,["@write","@hyperloglog","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["msetnx",-3,["write","denyoom"],1,-1,2,["@write","@string","@slow"],["request_policy:multi_shard","response_policy:agg_min"],[["flags",["OW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",2,"limit",0]]]],[]],["unlink",-2,["write","fast"],1,-1,1,["@keyspace","@write","@fast"],["request_policy:multi_shard","response_policy:agg_sum"],[["flags",["RM","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["bgsave",-1,["admin","noscript","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["zmscore",-3,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["smembers",2,["readonly"],1,1,1,["@read","@set","@slow"],["nondeterministic_output_order"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["echo",2,["loading","stale","fast"],0,0,0,["@fast","@connection"],[],[],[]],["decr",2,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["shutdown",-1,["admin","noscript","loading","stale","no_multi","allow_busy"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["rename",3,["write"],1,2,1,["@keyspace","@write","@slow"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lpop",-2,["write","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zrevrange",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hget",3,["readonly","fast"],1,1,1,["@read","@hash","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["brpoplpush",4,["write","denyoom","noscript","blocking"],1,2,1,["@write","@list","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["xrevrange",-4,["readonly"],1,1,1,["@read","@stream","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["persist",2,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["fcall_ro",-3,["readonly","noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["notes","We cannot tell how the keys will be used so we assume the worst, RO and ACCESS","flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["setbit",4,["write","denyoom"],1,1,1,["@write","@bitmap","@slow"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["srandmember",-2,["readonly"],1,1,1,["@read","@set","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["geohash",-2,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["incrbyfloat",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["rpoplpush",3,["write","denyoom"],1,2,1,["@write","@list","@slow"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RW","insert"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["exec",1,["noscript","loading","stale","skip_slowlog"],0,0,0,["@slow","@transaction"],[],[],[]],["xread",-4,["readonly","blocking","movablekeys"],0,0,0,["@read","@stream","@slow","@blocking"],[],[["flags",["RO","access"],"begin_search",["type","keyword","spec",["keyword","STREAMS","startfrom",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",2]]]],[]],["memory",-2,[],0,0,0,["@slow"],[],[],[["memory|purge",2,[],0,0,0,["@slow"],["request_policy:all_shards","response_policy:all_succeeded"],[],[]],["memory|malloc-stats",2,[],0,0,0,["@slow"],["nondeterministic_output","request_policy:all_shards","response_policy:special"],[],[]],["memory|doctor",2,[],0,0,0,["@slow"],["nondeterministic_output","request_policy:all_shards","response_policy:special"],[],[]],["memory|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["memory|stats",2,[],0,0,0,["@slow"],["nondeterministic_output","request_policy:all_shards","response_policy:special"],[],[]],["memory|usage",-3,["readonly"],2,2,1,["@read","@slow"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]]]],["georadius",-6,["write","denyoom","movablekeys"],1,1,1,["@write","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","keyword","spec",["keyword","STORE","startfrom",6]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["OW","update"],"begin_search",["type","keyword","spec",["keyword","STOREDIST","startfrom",6]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["save",1,["admin","noscript","no_async_loading","no_multi"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["hsetnx",4,["write","denyoom","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lastsave",1,["loading","stale","fast"],0,0,0,["@admin","@fast","@dangerous"],["nondeterministic_output"],[],[]],["zrangebyscore",-4,["readonly"],1,1,1,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["eval",-3,["noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["notes","We cannot tell how the keys will be used so we assume the worst, RW and UPDATE","flags",["RW","access","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["sunionstore",-3,["write","denyoom"],1,-1,1,["@write","@set","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["hexists",3,["readonly","fast"],1,1,1,["@read","@hash","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zmpop",-4,["write","movablekeys"],0,0,0,["@write","@sortedset","@slow"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["cluster",-2,[],0,0,0,["@slow"],[],[],[["cluster|keyslot",3,["stale"],0,0,0,["@slow"],[],[],[]],["cluster|setslot",-4,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|links",2,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|saveconfig",2,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|reset",-2,["admin","noscript","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|set-config-epoch",3,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|slaves",3,["admin","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output"],[],[]],["cluster|shards",2,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|delslotsrange",-4,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|bumpepoch",2,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output"],[],[]],["cluster|replicate",3,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|info",2,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|getkeysinslot",4,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|replicas",3,["admin","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output"],[],[]],["cluster|flushslots",2,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|addslotsrange",-4,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|meet",-4,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|delslots",-3,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["cluster|addslots",-3,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|slots",2,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|nodes",2,["stale"],0,0,0,["@slow"],["nondeterministic_output"],[],[]],["cluster|countkeysinslot",3,["stale"],0,0,0,["@slow"],[],[],[]],["cluster|count-failure-reports",3,["admin","stale"],0,0,0,["@admin","@slow","@dangerous"],["nondeterministic_output"],[],[]],["cluster|forget",3,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|failover",-2,["admin","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["cluster|myid",2,["stale"],0,0,0,["@slow"],[],[],[]]]],["scard",2,["readonly","fast"],1,1,1,["@read","@set","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["slaveof",3,["admin","noscript","stale","no_async_loading"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["rpop",-2,["write","fast"],1,1,1,["@write","@list","@fast"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zrangestore",-5,["write","denyoom"],1,2,1,["@write","@sortedset","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["select",2,["loading","stale","fast"],0,0,0,["@fast","@connection"],[],[],[]],["zrevrank",3,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hset",-4,["write","denyoom","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pubsub",-2,[],0,0,0,["@slow"],[],[],[["pubsub|channels",-2,["pubsub","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["pubsub|help",2,["loading","stale"],0,0,0,["@slow"],[],[],[]],["pubsub|numsub",-2,["pubsub","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["pubsub|shardchannels",-2,["pubsub","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["pubsub|numpat",2,["pubsub","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]],["pubsub|shardnumsub",-2,["pubsub","loading","stale"],0,0,0,["@pubsub","@slow"],[],[],[]]]],["zrank",3,["readonly","fast"],1,1,1,["@read","@sortedset","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["role",1,["noscript","loading","stale","fast"],0,0,0,["@admin","@fast","@dangerous"],[],[],[]],["zunion",-3,["readonly","movablekeys"],0,0,0,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["getbit",3,["readonly","fast"],1,1,1,["@read","@bitmap","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pexpireat",-3,["write","fast"],1,1,1,["@keyspace","@write","@fast"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["setnx",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["OW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["blmpop",-5,["write","blocking","movablekeys"],0,0,0,["@write","@list","@slow","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["xreadgroup",-7,["write","blocking","movablekeys"],0,0,0,["@write","@stream","@slow","@blocking"],[],[["flags",["RO","access"],"begin_search",["type","keyword","spec",["keyword","STREAMS","startfrom",4]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",2]]]],[]],["bitfield_ro",-2,["readonly","fast"],1,1,1,["@read","@bitmap","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["lset",4,["write","denyoom"],1,1,1,["@write","@list","@slow"],[],[["flags",["RW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["append",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["keys",2,["readonly"],0,0,0,["@keyspace","@read","@slow","@dangerous"],["request_policy:all_shards","nondeterministic_output_order"],[],[]],["xpending",-3,["readonly"],1,1,1,["@read","@stream","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["hincrbyfloat",4,["write","denyoom","fast"],1,1,1,["@write","@hash","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["mget",-2,["readonly","fast"],1,-1,1,["@read","@string","@fast"],["request_policy:multi_shard"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["evalsha",-3,["noscript","stale","skip_monitor","no_mandatory_keys","movablekeys"],0,0,0,["@slow","@scripting"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["psetex",4,["write","denyoom"],1,1,1,["@write","@string","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["zintercard",-3,["readonly","movablekeys"],0,0,0,["@read","@sortedset","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["readwrite",1,["loading","stale","fast"],0,0,0,["@fast","@connection"],[],[],[]],["monitor",1,["admin","noscript","loading","stale"],0,0,0,["@admin","@slow","@dangerous"],[],[],[]],["bzpopmax",-3,["write","noscript","blocking","fast"],1,-2,1,["@write","@sortedset","@fast","@blocking"],[],[["flags",["RW","access","delete"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-2,"keystep",1,"limit",0]]]],[]],["sort_ro",-2,["readonly","movablekeys"],1,1,1,["@read","@set","@sortedset","@list","@slow","@dangerous"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["notes","For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort","flags",["RO","access"],"begin_search",["type","unknown","spec",[]],"find_keys",["type","unknown","spec",[]]]],[]],["zdiffstore",-4,["write","denyoom","movablekeys"],1,1,1,["@write","@sortedset","@slow"],[],[["flags",["OW","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","keynum","spec",["keynumidx",0,"firstkey",1,"keystep",1]]]],[]],["getset",3,["write","denyoom","fast"],1,1,1,["@write","@string","@fast"],[],[["flags",["RW","access","update"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["pfmerge",-2,["write","denyoom"],1,-1,1,["@write","@hyperloglog","@slow"],[],[["flags",["RW","access","insert"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]],["flags",["RO","access"],"begin_search",["type","index","spec",["index",2]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]],["georadiusbymember_ro",-5,["readonly"],1,1,1,["@read","@geo","@slow"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["expiretime",2,["readonly","fast"],1,1,1,["@keyspace","@read","@fast"],[],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["type",2,["readonly","fast"],1,1,1,["@keyspace","@read","@fast"],[],[["flags",["RO"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["sscan",-3,["readonly"],1,1,1,["@read","@set","@slow"],["nondeterministic_output"],[["flags",["RO","access"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",0,"keystep",1,"limit",0]]]],[]],["ssubscribe",-2,["pubsub","noscript","loading","stale"],1,-1,1,["@pubsub","@slow"],[],[["flags",["not_key"],"begin_search",["type","index","spec",["index",1]],"find_keys",["type","range","spec",["lastkey",-1,"keystep",1,"limit",0]]]],[]]]src/lib/redis-version.mjs000066400000000000000000000013071517716222300157260ustar00rootroot00000000000000/** * Parse a Redis server version string (e.g. '8.6.2') and provide * feature-gating helpers. * * Usage: * const rv = parseRedisVersion(infoData?.server?.redis_version) * if (rv.isAtLeast(8, 2)) { // use XDELEX } */ export function parseRedisVersion(versionStr) { if (!versionStr) return { major: 0, minor: 0, patch: 0, isAtLeast: () => false, raw: '' } const parts = versionStr.split('.').map(Number) const major = parts[0] || 0 const minor = parts[1] || 0 const patch = parts[2] || 0 return { major, minor, patch, isAtLeast: (reqMajor, reqMinor) => major > reqMajor || (major === reqMajor && minor >= reqMinor), raw: versionStr, } } src/lib/resolve-version.mjs000066400000000000000000000040751517716222300163040ustar00rootroot00000000000000import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const serverPkg = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) let redisUiPkg = null // Resolve p3x-redis-ui package.json. // Works in: Docker, Electron (asar), yarn workspaces, npm flat/nested node_modules. try { // Strategy 1: walk up from __dirname (works for nested node_modules, Docker, Electron) let dir = __dirname for (let i = 0; i < 10; i++) { dir = path.dirname(dir) const candidate = path.join(dir, 'package.json') if (fs.existsSync(candidate)) { const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8')) if (parsed.name === 'p3x-redis-ui') { redisUiPkg = parsed break } } if (dir === path.dirname(dir)) break } // Strategy 2: scan sibling directories (flat node_modules, dev workspace) if (!redisUiPkg) { const serverRoot = path.resolve(__dirname, '../..') const parentDir = path.dirname(serverRoot) try { const siblings = fs.readdirSync(parentDir) for (const sibling of siblings) { const candidate = path.join(parentDir, sibling, 'package.json') if (fs.existsSync(candidate)) { const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8')) if (parsed.name === 'p3x-redis-ui') { redisUiPkg = parsed break } } } } catch { /* parentDir not readable */ } } } catch (e) { console.warn('resolve-version: error resolving version', e.message) } if (!redisUiPkg) { console.info('resolve-version: p3x-redis-ui package not found, using server version') } export const isSnapshot = !redisUiPkg export const version = redisUiPkg?.version ?? serverPkg.version export const serverVersion = serverPkg.version export const pkg = redisUiPkg ?? serverPkg src/service/000077500000000000000000000000001517716222300133135ustar00rootroot00000000000000src/service/decompress.mjs000066400000000000000000000105361517716222300161770ustar00rootroot00000000000000import zlib from 'node:zlib' import snappyjs from 'snappyjs' import lz4 from 'lz4js' import { execFileSync } from 'node:child_process' /** * Detect compression format by magic bytes. * Returns { algorithm, decompressed } or null if not compressed. */ export async function tryDecompress(buffer) { if (!Buffer.isBuffer(buffer) || buffer.length < 2) return null const b0 = buffer[0] const b1 = buffer[1] // GZIP: 1F 8B if (b0 === 0x1f && b1 === 0x8b) { try { const result = zlib.gunzipSync(buffer) if (isLikelyUtf8(result)) return { algorithm: 'gzip', decompressed: result } } catch {} return null } // ZIP (PKZip): 50 4B 03 04 if (buffer.length >= 4 && b0 === 0x50 && b1 === 0x4b && buffer[2] === 0x03 && buffer[3] === 0x04) { try { const result = await decompressZip(buffer) if (result && isLikelyUtf8(result)) return { algorithm: 'zip', decompressed: result } } catch {} return null } // zlib/deflate: first byte 0x78, CMF/FLG checksum: (0x78 * 256 + b1) % 31 === 0 if (b0 === 0x78 && (0x78 * 256 + b1) % 31 === 0) { try { const result = zlib.inflateSync(buffer) if (isLikelyUtf8(result)) return { algorithm: 'zlib', decompressed: result } } catch {} return null } // Zstandard: 28 B5 2F FD if (buffer.length >= 4 && b0 === 0x28 && b1 === 0xb5 && buffer[2] === 0x2f && buffer[3] === 0xfd) { try { const result = execFileSync('zstd', ['-d'], { input: buffer, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'ignore'] }) if (isLikelyUtf8(result)) return { algorithm: 'zstd', decompressed: result } } catch (e) { // zstd may exit non-zero but still produce output if (e.stdout && e.stdout.length > 0 && isLikelyUtf8(e.stdout)) { return { algorithm: 'zstd', decompressed: e.stdout } } } return null } // LZ4 frame: 04 22 4D 18 if (buffer.length >= 4 && b0 === 0x04 && b1 === 0x22 && buffer[2] === 0x4d && buffer[3] === 0x18) { try { const result = Buffer.from(lz4.decompress(buffer)) if (isLikelyUtf8(result)) return { algorithm: 'lz4', decompressed: result } } catch {} return null } // Snappy + Brotli: no reliable magic bytes β€” always try, validate output try { const result = Buffer.from(snappyjs.uncompress(buffer)) if (result.length > 0 && isLikelyUtf8(result)) { return { algorithm: 'snappy', decompressed: result } } } catch { /* not snappy */ } try { const result = zlib.brotliDecompressSync(buffer) if (result.length > 0 && isLikelyUtf8(result)) { return { algorithm: 'brotli', decompressed: result } } } catch { /* not brotli */ } return null } /** * Extract the first file from a ZIP archive using raw inflate (no external dependency). * ZIP local file header: signature 50 4B 03 04, then metadata, then compressed data. */ async function decompressZip(buffer) { if (buffer.length < 30) return null // Local file header offsets const compressionMethod = buffer.readUInt16LE(8) const compressedSize = buffer.readUInt32LE(18) const filenameLen = buffer.readUInt16LE(26) const extraLen = buffer.readUInt16LE(28) const dataOffset = 30 + filenameLen + extraLen if (dataOffset + compressedSize > buffer.length) return null const compressedData = buffer.subarray(dataOffset, dataOffset + compressedSize) if (compressionMethod === 0) { // Stored (no compression) return Buffer.from(compressedData) } if (compressionMethod === 8) { // Deflated β€” use raw inflate (no zlib header) return zlib.inflateRawSync(compressedData) } return null } /** * Quick check if buffer looks like valid UTF-8 text. * Checks first 64 bytes for control characters (except common ones). */ function isLikelyUtf8(buffer) { const checkLen = Math.min(buffer.length, 64) for (let i = 0; i < checkLen; i++) { const b = buffer[i] // Allow: tab (9), newline (10), carriage return (13), printable ASCII (32-126), and UTF-8 continuation bytes (128+) if (b < 9 || (b > 13 && b < 32) || b === 127) { return false } } return true } src/service/http/000077500000000000000000000000001517716222300142725ustar00rootroot00000000000000src/service/http/index.mjs000066400000000000000000000300641517716222300161170ustar00rootroot00000000000000import express from 'express' import fs from 'fs' import path from 'path' import http from 'http' import { fileURLToPath } from 'url' import { resolveConfiguredHttpAuth, verifyCredentials, createAuthToken, verifyAuthToken } from '../../lib/http-auth.mjs' import { version } from '../../lib/resolve-version.mjs' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const httpService = function () { const self = this self.boot = async () => { const app = express() this.app = app app.disable('x-powered-by') // Content Security Policy β€” covers Docker, direct server, and any deployment without a reverse proxy app.use((req, res, next) => { res.set('Content-Security-Policy', "default-src 'self'; script-src 'self' https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.googletagmanager.com https://www.google-analytics.com; font-src 'self' data:; connect-src 'self' ws: wss: http://localhost:* http://127.0.0.1:* https://www.googletagmanager.com https://www.google-analytics.com https://region1.google-analytics.com https://analytics.google.com; object-src 'none'; base-uri 'self'; form-action 'self'") next() }) // Health endpoint β€” before auth, always accessible const startedAt = new Date().toISOString() app.get('/health', (req, res) => { res.json({ status: 'ok', version: version, uptime: process.uptime(), startedAt: startedAt, }) }) // CORS for API endpoints (needed in dev when frontend runs on a different port) app.use('/api', (req, res, next) => { res.set('Access-Control-Allow-Origin', '*') res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') res.set('Access-Control-Allow-Headers', 'Content-Type') if (req.method === 'OPTIONS') return res.sendStatus(204) next() }) // Auth status β€” public, tells frontend if login is required app.get('/api/auth-status', (req, res) => { const httpAuth = resolveConfiguredHttpAuth() res.json({ enabled: httpAuth.enabled }) }) // Login endpoint β€” validates credentials, returns JWT token app.post('/api/login', express.json(), (req, res) => { const httpAuth = resolveConfiguredHttpAuth() if (!httpAuth.enabled) { return res.json({ status: 'ok', authRequired: false }) } const { username, password } = req.body || {} if (!username || !password) { return res.status(400).json({ status: 'error', error: 'credentials_required' }) } if (!verifyCredentials({ username, password })) { return res.status(401).json({ status: 'error', error: 'invalid_credentials' }) } const token = createAuthToken(username) res.json({ status: 'ok', token }) }) // Token verify β€” checks if a stored token is still valid app.post('/api/verify-token', express.json(), (req, res) => { const httpAuth = resolveConfiguredHttpAuth() if (!httpAuth.enabled) { return res.json({ valid: true, authRequired: false }) } const { token } = req.body || {} const payload = verifyAuthToken(token) res.json({ valid: !!payload }) }) const findModulePath = (startPath, targetPath) => { let currentPath = startPath while (currentPath !== path.resolve(currentPath, '..')) { const nodeModulesPath = path.join(currentPath, targetPath) if (fs.existsSync(nodeModulesPath)) { return nodeModulesPath } currentPath = path.resolve(currentPath, '..') } throw new Error('The specified module could not be found in any node_modules directory') } const resolvePath = (inputPath) => { if (inputPath.startsWith('~')) { const inputPathFromNodeModules = inputPath.substring(1) const startPath = __dirname return findModulePath(startPath, inputPathFromNodeModules) } return path.resolve(process.cwd(), inputPath) } // Mount Angular at /ng/ let hasNg = false let ngPath const ngStatic = p3xrs.cfg.static || p3xrs.cfg.staticNg if (typeof ngStatic === 'string') { try { ngPath = resolvePath(ngStatic) app.use('/ng', express.static(ngPath, { etag: true, lastModified: true, setHeaders: (res, filePath) => { if (filePath.endsWith('.html')) res.setHeader('Cache-Control', 'no-cache') } })) hasNg = true console.info('Angular static mounted at /ng/ from', ngPath) } catch (e) { console.warn('Could not resolve Angular static path:', ngStatic, '-', e.message) } } // Mount React at /react/ // Auto-detect: if Angular is at /path/public, React is at /path/public-react let hasReact = false let reactPath let reactStatic = p3xrs.cfg.staticReact if (!reactStatic && ngPath) { const autoReactPath = ngPath + '-react' if (fs.existsSync(autoReactPath)) { reactStatic = autoReactPath } } if (!reactStatic) { reactStatic = '~p3x-redis-ui-material/dist-react' } if (typeof reactStatic === 'string') { try { reactPath = reactStatic.startsWith('~') ? resolvePath(reactStatic) : reactStatic if (fs.existsSync(reactPath)) { app.use('/react', express.static(reactPath, { etag: true, lastModified: true, setHeaders: (res, filePath) => { if (filePath.endsWith('.html')) res.setHeader('Cache-Control', 'no-cache') } })) hasReact = true console.info('React static mounted at /react/ from', reactPath) } } catch (e) { // React build may not exist yet β€” that's ok } } // Mount Vue at /vue/ let hasVue = false let vuePath let vueStatic = p3xrs.cfg.staticVue if (!vueStatic && ngPath) { const autoVuePath = ngPath + '-vue' if (fs.existsSync(autoVuePath)) { vueStatic = autoVuePath } } if (!vueStatic) { vueStatic = '~p3x-redis-ui-material/dist-vue' } if (typeof vueStatic === 'string') { try { vuePath = vueStatic.startsWith('~') ? resolvePath(vueStatic) : vueStatic if (fs.existsSync(vuePath)) { app.use('/vue', express.static(vuePath, { etag: true, lastModified: true, setHeaders: (res, filePath) => { if (filePath.endsWith('.html')) res.setHeader('Cache-Control', 'no-cache') } })) hasVue = true console.info('Vue static mounted at /vue/ from', vuePath) } } catch (e) { // Vue build may not exist yet β€” that's ok } } // Pre-read index.html files so SPA fallback works inside .asar archives // (res.sendFile uses the `send` library which breaks inside .asar) // For non-asar (server) deployments, re-read from disk each time so // deploys that update files after server start don't serve stale HTML. const isAsar = (p) => p.includes('.asar') let ngIndexHtml const ngIndexPath = hasNg ? path.resolve(ngPath, 'index.html') : null if (hasNg) { ngIndexHtml = await fs.promises.readFile(ngIndexPath, 'utf8') } let reactIndexHtml const reactIndexPath = hasReact ? path.resolve(reactPath, 'index.html') : null if (hasReact) { reactIndexHtml = await fs.promises.readFile(reactIndexPath, 'utf8') } let vueIndexHtml const vueIndexPath = hasVue ? path.resolve(vuePath, 'index.html') : null if (hasVue) { vueIndexHtml = await fs.promises.readFile(vueIndexPath, 'utf8') } const getNgIndexHtml = async () => { if (isAsar(ngIndexPath)) return ngIndexHtml return fs.promises.readFile(ngIndexPath, 'utf8') } const getReactIndexHtml = async () => { if (isAsar(reactIndexPath)) return reactIndexHtml return fs.promises.readFile(reactIndexPath, 'utf8') } const getVueIndexHtml = async () => { if (isAsar(vueIndexPath)) return vueIndexHtml return fs.promises.readFile(vueIndexPath, 'utf8') } const noCacheHeaders = (res) => res.set('Cache-Control', 'no-cache') // Root / β†’ redirect based on localStorage preference (client-side) if (hasNg || hasReact || hasVue) { app.get('/', (req, res) => { noCacheHeaders(res) res.type('html').send(`P3X Redis UI`) }) } // SPA fallback for /ng/* routes if (hasNg) { app.use('/ng', async (req, res, next) => { if (req.path.startsWith('/socket.io')) { next() return } noCacheHeaders(res) res.type('html').send(await getNgIndexHtml()) }) } // SPA fallback for /react/* routes if (hasReact) { app.use('/react', async (req, res, next) => { if (req.path.startsWith('/socket.io')) { next() return } noCacheHeaders(res) res.type('html').send(await getReactIndexHtml()) }) } // SPA fallback for /vue/* routes if (hasVue) { app.use('/vue', async (req, res, next) => { if (req.path.startsWith('/socket.io')) { next() return } noCacheHeaders(res) res.type('html').send(await getVueIndexHtml()) }) } // Fallback when no frontends are available if (!hasNg && !hasReact && !hasVue) { app.use((req, res) => { res.json({ status: 'operational' }) }) } app.use((error, req, res, next) => { console.error('express server error', error) if (res.headersSent) { next(error) return } res.status(500).json({ error: 'internal_server_error', }) }) const server = http.createServer(app) this.server = server server.listen(p3xrs.cfg.http.port || 7843, p3xrs.cfg.http.bind ? p3xrs.cfg.http.bind : '0.0.0.0') } } export default httpService src/service/index.mjs000066400000000000000000000001021517716222300151260ustar00rootroot00000000000000import http from './http/index.mjs' export default { http, } src/service/socket.io/000077500000000000000000000000001517716222300152115ustar00rootroot00000000000000src/service/socket.io/index.mjs000066400000000000000000000020371517716222300170350ustar00rootroot00000000000000import { Server } from 'socket.io' import { resolveConfiguredHttpAuth, verifyAuthToken } from '../../lib/http-auth.mjs' import socketHandler from './socket.mjs' const socketIoService = function () { const self = this; self.boot = async (options) => { const httpService = options.httpService const socketio = new Server(httpService.server, { secure: true, path: '/socket.io', maxHttpBufferSize: 256 * 1024 * 1024, // 256 MB }); socketio.use((socket, next) => { const httpAuth = resolveConfiguredHttpAuth() if (!httpAuth.enabled) { next() return } const token = socket.handshake.auth?.token if (token && verifyAuthToken(token)) { next() return } const error = new Error('auth_required') next(error) }) socketHandler(socketio); this.socketio = socketio } } export default socketIoService src/service/socket.io/request/000077500000000000000000000000001517716222300167015ustar00rootroot00000000000000src/service/socket.io/request/acl/000077500000000000000000000000001517716222300174405ustar00rootroot00000000000000src/service/socket.io/request/acl/del-user.mjs000066400000000000000000000026761517716222300217060ustar00rootroot00000000000000import { ensureReadonlyConnection } from '../../shared.mjs' export default async (options) => { const { socket, payload } = options try { ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } if (!payload.username) { socket.emit(options.responseEvent, { status: 'error', error: 'Username is required' }) return } if (payload.username === 'default') { socket.emit(options.responseEvent, { status: 'error', error: 'Cannot delete the default user' }) return } const whoami = await redis.call('ACL', 'WHOAMI') if (payload.username === whoami) { socket.emit(options.responseEvent, { status: 'error', error: 'Cannot delete the currently connected user' }) return } // Use cluster-aware method if available, otherwise direct call if (typeof redis.aclDeluser === 'function') { await redis.aclDeluser(payload.username) } else { await redis.call('ACL', 'DELUSER', payload.username) } socket.emit(options.responseEvent, { status: 'ok' }) } catch (e) { console.error('acl/del-user failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/acl/get-user.mjs000066400000000000000000000013131517716222300217040ustar00rootroot00000000000000export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } const info = await redis.call('ACL', 'GETUSER', payload.username) const result = {} for (let i = 0; i < info.length; i += 2) { result[info[i]] = info[i + 1] } socket.emit(options.responseEvent, { status: 'ok', data: result }) } catch (e) { console.error('acl/get-user failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/acl/list.mjs000066400000000000000000000021141517716222300211240ustar00rootroot00000000000000export default async (options) => { const { socket } = options try { const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } const [users, whoami] = await Promise.all([ redis.call('ACL', 'LIST'), redis.call('ACL', 'WHOAMI'), ]) const parsed = users.map(line => { const parts = line.split(' ') const name = parts[1] return { name, raw: line, enabled: line.includes(' on '), allKeys: line.includes(' ~* ') || line.includes(' allkeys'), allCommands: line.includes(' +@all') || line.includes(' allcommands'), } }) socket.emit(options.responseEvent, { status: 'ok', data: { users: parsed, currentUser: whoami } }) } catch (e) { console.error('acl/list failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/acl/set-user.mjs000066400000000000000000000025251517716222300217260ustar00rootroot00000000000000import { ensureReadonlyConnection } from '../../shared.mjs' export default async (options) => { const { socket, payload } = options try { ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } if (!payload.username || !payload.rules) { socket.emit(options.responseEvent, { status: 'error', error: 'Username and rules are required' }) return } if (!Array.isArray(payload.rules) || !payload.rules.every(r => typeof r === 'string' && r.length > 0)) { socket.emit(options.responseEvent, { status: 'error', error: 'Rules must be an array of non-empty strings' }) return } // Use cluster-aware method if available, otherwise direct call if (typeof redis.aclSetuser === 'function') { await redis.aclSetuser(payload.username, ...payload.rules) } else { await redis.call('ACL', 'SETUSER', payload.username, ...payload.rules) } socket.emit(options.responseEvent, { status: 'ok' }) } catch (e) { console.error('acl/set-user failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/ai/000077500000000000000000000000001517716222300172725ustar00rootroot00000000000000src/service/socket.io/request/ai/redis-query.mjs000066400000000000000000000317471517716222300222720ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' import { TOOL_SCHEMAS, runTool } from './tools.mjs' import { buildSystemPrompt, callGroq, cleanAiText, parseAiResponse, summarizeMessages, truncateToolContent, } from '../../../../lib/ai/prompt.mjs' const parser = sharedIoRedis.argumentParser // Max number of tool-call rounds per AI turn. Prevents runaway loops. const MAX_AGENTIC_ITERATIONS = 5 // Hard cap on parallel tool calls within a single assistant turn. const MAX_TOOL_CALLS_PER_TURN = 10 // Per-tool-result cap handled by the shared truncateToolContent() default (8000 chars). // Total messages[] cap across one turn (~6K tokens). Groq free-tier TPM is 8K, // leave headroom for the assistant's completion and the system prompt drift. const MAX_TOTAL_MESSAGES_CHARS = 24000 const AI_NETWORK_URL_PROD = 'https://network.corifeus.com' const AI_NETWORK_URL_DEV = 'http://localhost:8003' // LANGUAGE_NAMES, buildLanguageInstruction, SYSTEM_PROMPT, LIMITED_AI_SYSTEM_PROMPT, // and buildSystemPrompt now live in src/ai/prompt.mjs and are imported above. // They are shared with network.corifeus.com (which syncs the file at build time). // Edit the prompt in src/ai/prompt.mjs β€” NEVER copy it back into this file. function getNetworkUrl() { if (typeof p3xrs.cfg.aiNetworkUrl === 'string' && p3xrs.cfg.aiNetworkUrl.length > 0) { return p3xrs.cfg.aiNetworkUrl } const isDev = process.env.NODE_ENV === 'development' return isDev ? AI_NETWORK_URL_DEV : AI_NETWORK_URL_PROD } const disabledCommands = ['subscribe', 'monitor', 'quit', 'psubscribe'] // Commands that have cluster-aware overrides on the Cluster class. // Using redis.call() bypasses these overrides, so we call the method directly. const clusterOverriddenCommands = { flushdb: 'flushdb', flushall: 'flushall', dbsize: 'dbsize', } async function executeRedisCommand(redis, commandStr) { const tokens = parser(commandStr) if (tokens.length === 0) throw new Error('Empty command') const mainCommand = tokens.shift().toLowerCase() if (disabledCommands.includes(mainCommand)) { throw new Error(`Command '${mainCommand}' is not allowed`) } // Use the instance method for cluster-overridden commands so the // Cluster subclass can broadcast to all master nodes. const overrideMethod = clusterOverriddenCommands[mainCommand] if (overrideMethod && typeof redis[overrideMethod] === 'function') { return await redis[overrideMethod](...tokens) } return await redis.call(mainCommand, ...tokens) } /** * Single Groq chat completion β€” either direct (own key) or via network proxy. * For tool-use, the server drives the loop locally (it has the Redis connection); * this function just returns the raw assistant message from one round-trip. */ async function callGroqMessages({ messages, tools, apiKey, useOwnKey }) { const model = 'openai/gpt-oss-120b' const maxTokens = p3xrs.cfg.groqMaxTokens || 65536 if (useOwnKey && apiKey) { // Direct: use the shared callGroq wrapper (same one network.corifeus.com uses). const completion = await callGroq({ messages, tools, apiKey, model, maxTokens }) return completion.choices?.[0]?.message || {} } const networkUrl = getNetworkUrl() let response try { response = await fetch(`${networkUrl}/public/ai/redis-query`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ // Tool-use / agentic path: the client drives the loop, the proxy // forwards the full message history + tool schemas to Groq and // returns the raw assistant message. Legacy fields stay for // backward compatibility with older proxy versions. messages, tools: tools && tools.length > 0 ? tools : undefined, apiKey: apiKey || undefined, }), }) } catch { throw new Error('AI service is not reachable') } const contentType = response.headers.get('content-type') || '' if (!contentType.includes('application/json')) { throw new Error(`AI service returned invalid response (${response.status})`) } const data = await response.json() if (data.status !== 'ok') { throw new Error(data.message || 'AI query failed') } // Tool-capable proxy response: data.data.message = full Groq message object. // Legacy proxy response: data.data = { command, explanation } β€” wrap it. if (data.data?.message) return data.data.message if (data.data?.command !== undefined) { return { role: 'assistant', content: (data.data.command || '') + (data.data.explanation ? '\n---\n' + data.data.explanation : ''), } } return { role: 'assistant', content: '' } } const messagesCharCount = (messages) => summarizeMessages(messages).chars // Always compress older tool results before each Groq call, regardless of // total size. Only the most-recent batch (tool results that follow the last // assistant-with-tool_calls message) is kept in full β€” the AI needs that // detail to decide its next move. Everything earlier becomes a one-line // breadcrumb so the conversation can never balloon, even across many rounds. function compressOlderToolResults(messages) { let lastAsstToolsIdx = -1 for (let i = messages.length - 1; i >= 0; i--) { const m = messages[i] if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) { lastAsstToolsIdx = i break } } for (let i = 0; i < lastAsstToolsIdx; i++) { const m = messages[i] if (m.role !== 'tool' || typeof m.content !== 'string') continue if (m.content.startsWith('[prior tool result')) continue const preview = m.content.slice(0, 150).replace(/\s+/g, ' ') m.content = `[prior tool result summarized: ${preview}${m.content.length > 150 ? '...' : ''}]` } } // Safety net: if compression left things still too large (e.g. a single huge // current tool result combined with a long system prompt), fall back to // summarizing everything older than the latest assistant message. function enforceMessagesBudget(messages) { if (messagesCharCount(messages) <= MAX_TOTAL_MESSAGES_CHARS) return for (let i = 0; i < messages.length; i++) { if (messagesCharCount(messages) <= MAX_TOTAL_MESSAGES_CHARS) return const m = messages[i] if (m.role !== 'tool') continue if (typeof m.content === 'string' && m.content.length > 200) { m.content = `[earlier tool result β€” summarized: ${m.content.slice(0, 150)}...]` } } } /** * Agentic loop β€” asks Groq, executes any tool calls locally against the user's * Redis connection, feeds results back, repeats up to MAX_AGENTIC_ITERATIONS. * Returns { command, explanation, toolTrail }. * * Tools are only offered when `redis` is available (connected) AND context * indicates we are in connected mode. Limited/disconnected mode uses the * existing shorter prompt with no tools. */ async function runAgenticLoop({ prompt, context, apiKey, useOwnKey, redis }) { const systemPrompt = buildSystemPrompt(context, { includeToolUse: true }) const messages = [ { role: 'system', content: systemPrompt }, { role: 'user', content: prompt }, ] const toolsAvailable = redis && context?.connectionState === 'connected' const tools = toolsAvailable ? TOOL_SCHEMAS : [] const toolTrail = [] for (let iter = 0; iter < MAX_AGENTIC_ITERATIONS; iter++) { compressOlderToolResults(messages) enforceMessagesBudget(messages) const assistantMessage = await callGroqMessages({ messages, tools, apiKey, useOwnKey }) messages.push(assistantMessage) const toolCalls = assistantMessage.tool_calls || [] if (toolCalls.length === 0) { // Final answer β€” parse command + explanation from content const content = (assistantMessage.content || '').trim() const parsed = parseAiResponse(content) return { ...parsed, toolTrail } } // Execute each tool call (capped), append tool results as tool-role messages. // Hard guard: if there is no live Redis connection, refuse to run tools β€” this // should be unreachable because we pass `tools: []` when toolsAvailable is // false, but the model might still request tools on older proxies. Fail safe. const callsToRun = toolCalls.slice(0, MAX_TOOL_CALLS_PER_TURN) for (const call of callsToRun) { let args = {} try { args = JSON.parse(call.function?.arguments || '{}') } catch { args = {} } const name = call.function?.name || '' let exec if (!toolsAvailable) { exec = { ok: false, error: 'Not connected to Redis β€” tools are unavailable.', ms: 0 } } else { exec = await runTool(redis, name, args) } toolTrail.push({ name, args, ok: exec.ok, result: exec.result, error: exec.error, ms: exec.ms, }) messages.push({ role: 'tool', tool_call_id: call.id, content: exec.ok ? truncateToolContent(exec.result) : `ERROR: ${exec.error}`, }) } // Loop again so the model can react to the tool results. } // Hit the iteration cap without a final answer β€” synthesize a fallback. return { command: '', explanation: 'AI investigation exceeded the tool-call limit without reaching a conclusion. Partial tool trail below.', toolTrail, } } export default async (options) => { const { socket, payload } = options try { const { prompt, context, execute } = payload if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) { throw new Error('AI_PROMPT_REQUIRED') } if (prompt.length > 4096) { throw new Error('AI prompt too long (max 4096 characters)') } if (p3xrs.cfg.aiEnabled === false) { throw new Error('AI_DISABLED') } const apiKey = p3xrs.cfg.groqApiKey || '' const useOwnKey = p3xrs.cfg.aiUseOwnKey === true // Only pass a Redis client into the agentic loop when BOTH the client // reports connected state AND the socket has a live ioredis. Stale // ioredis (from a prior disconnected session) must not be used. const redis = (context?.connectionState === 'connected' && socket.p3xrs?.ioredis) ? socket.p3xrs.ioredis : null console.info( useOwnKey && apiKey ? 'ai-redis-query: using direct Groq API (own key)' : 'ai-redis-query: using network proxy', 'β€” tools', redis ? 'enabled' : 'disabled', ) const result = await runAgenticLoop({ prompt: prompt.trim(), context, apiKey: apiKey || undefined, useOwnKey: useOwnKey && Boolean(apiKey), redis, }) const response = { status: 'ok', command: result.command, explanation: result.explanation, toolTrail: result.toolTrail, } // Execute commands if requested AND we have a live Redis connection. // The `redis` variable above is gated on connectionState==='connected'; // if absent, skip execution entirely β€” no stale client runs. if (execute && redis) { if (socket.p3xrs.readonly === true) { response.executed = false response.executionError = 'readonly-connection-mode' } else { const commandLines = result.command.split('\n').filter(line => line.trim().length > 0) const executionResults = [] for (const cmd of commandLines) { try { const cmdResult = await executeRedisCommand(redis, cmd) executionResults.push({ command: cmd, result: cmdResult }) } catch (execError) { executionResults.push({ command: cmd, error: execError.message }) } } response.executed = true response.results = executionResults } } socket.emit(options.responseEvent, response) } catch (e) { console.error('ai-redis-query error', e) let errorMsg = e.message || String(e) if (e.status === 403 || errorMsg.includes('blocked_api_access')) { errorMsg = 'blocked_api_access' } else if (e.status === 429 || errorMsg.includes('rate_limit')) { errorMsg = 'rate_limit' } socket.emit(options.responseEvent, { status: 'error', error: errorMsg, }) } } src/service/socket.io/request/ai/set-groq-api-key.mjs000066400000000000000000000041531517716222300231060ustar00rootroot00000000000000import fs from 'fs' export default async (options) => { const { socket, payload } = options try { if (p3xrs.cfg.groqApiKeyReadonly === true) { throw new Error('GROQ_API_KEY_READONLY') } const aiEnabled = payload.aiEnabled !== false const aiUseOwnKey = payload.aiUseOwnKey === true const groqMaxTokens = typeof payload.groqMaxTokens === 'number' && payload.groqMaxTokens > 0 ? payload.groqMaxTokens : (p3xrs.cfg.groqMaxTokens || 16384) // Only update API key if explicitly provided in payload const hasNewKey = payload.hasOwnProperty('apiKey') const apiKey = hasNewKey ? (payload.apiKey || '').trim() : undefined // Update runtime config if (hasNewKey) { p3xrs.cfg.groqApiKey = apiKey || undefined } p3xrs.cfg.aiEnabled = aiEnabled p3xrs.cfg.aiUseOwnKey = aiUseOwnKey p3xrs.cfg.groqMaxTokens = groqMaxTokens // Persist to p3xrs.json if (p3xrs.configPath) { try { const raw = fs.readFileSync(p3xrs.configPath, 'utf8') const config = JSON.parse(raw) if (!config.p3xrs || typeof config.p3xrs !== 'object') { config.p3xrs = {} } if (hasNewKey) { if (apiKey) { config.p3xrs.groqApiKey = apiKey } else { delete config.p3xrs.groqApiKey } } config.p3xrs.aiEnabled = aiEnabled config.p3xrs.aiUseOwnKey = aiUseOwnKey config.p3xrs.groqMaxTokens = groqMaxTokens fs.writeFileSync(p3xrs.configPath, JSON.stringify(config, null, 4)) } catch (e) { console.error('failed to persist AI settings', e.message) } } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/ai/tools.mjs000066400000000000000000000270441517716222300211540ustar00rootroot00000000000000/** * AI tool-use β€” read-only Redis tools the LLM can call to investigate live state. * * Used by the agentic loop in redis-query.mjs. Each tool has: * - schema: OpenAI-compatible tool definition passed to Groq * - run: executor that runs against the user's active Redis connection * * Safety rules (do NOT relax): * - All tools are read-only β€” no SET, DEL, CONFIG SET, FLUSHDB, SCRIPT, EVAL, SHUTDOWN * - SCAN is exposed (bounded COUNT), KEYS is never exposed * - Per-turn tool-call cap is enforced by the caller (agenticLoop) * - Results are truncated if huge β€” prevents token blowup */ const MAX_RESULT_CHARS = 8000 function truncate(s) { if (typeof s !== 'string') s = JSON.stringify(s) if (s.length <= MAX_RESULT_CHARS) return s return s.slice(0, MAX_RESULT_CHARS) + `\n… [truncated, original ${s.length} chars]` } function formatReply(reply) { if (reply === null || reply === undefined) return '' if (typeof reply === 'string') return truncate(reply) if (Buffer.isBuffer(reply)) return truncate(reply.toString('utf8')) if (Array.isArray(reply)) return truncate(JSON.stringify(reply, null, 2)) return truncate(JSON.stringify(reply, null, 2)) } export const TOOL_SCHEMAS = [ { type: 'function', function: { name: 'redis_info', description: 'Run INFO [section] to get server/memory/stats/replication/clients/cpu/keyspace data. Use this to answer "why is memory high", "how many clients", "what is the uptime".', parameters: { type: 'object', properties: { section: { type: 'string', description: 'Optional INFO section: server, clients, memory, stats, replication, cpu, modules, keyspace, all. Omit for default summary.', }, }, required: [], }, }, }, { type: 'function', function: { name: 'redis_memory_stats', description: 'Run MEMORY STATS for a detailed memory breakdown (overhead, per-type usage, allocator stats). Prefer this over INFO memory when the user asks about memory composition.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_slowlog_get', description: 'Run SLOWLOG GET [count] to retrieve the most recent slow queries with their execution duration. Use for "why is it slow", "what queries are slow".', parameters: { type: 'object', properties: { count: { type: 'integer', description: 'Max entries to return (default 16, max 128).', }, }, required: [], }, }, }, { type: 'function', function: { name: 'redis_client_list', description: 'Run CLIENT LIST to see all connected clients (address, name, current command, idle time).', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_config_get', description: 'Run CONFIG GET to read server configuration. Common patterns: maxmemory, maxmemory-policy, save, timeout, *, client-output-buffer-limit.', parameters: { type: 'object', properties: { pattern: { type: 'string', description: 'Config key glob, e.g. "maxmemory*" or "save".', }, }, required: ['pattern'], }, }, }, { type: 'function', function: { name: 'redis_dbsize', description: 'Run DBSIZE to count keys in the current database.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_latency_latest', description: 'Run LATENCY LATEST to see the most recent latency events recorded by the server.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_scan', description: 'Run SCAN [MATCH ] [COUNT ] [TYPE ] to enumerate keys safely. NEVER exposes KEYS. Default COUNT 100, max 1000.', parameters: { type: 'object', properties: { cursor: { type: 'string', description: 'Cursor (0 to start, use returned cursor to continue).' }, match: { type: 'string', description: 'Optional MATCH glob pattern.' }, count: { type: 'integer', description: 'Optional COUNT hint (1-1000, default 100).' }, type: { type: 'string', description: 'Optional TYPE filter: string, list, set, hash, zset, stream, ReJSON-RL.' }, }, required: ['cursor'], }, }, }, { type: 'function', function: { name: 'redis_type', description: 'Run TYPE to see the data type of a specific key.', parameters: { type: 'object', properties: { key: { type: 'string', description: 'The Redis key name.' }, }, required: ['key'], }, }, }, { type: 'function', function: { name: 'redis_ttl', description: 'Run TTL to see seconds until a key expires. -1 = no expiry, -2 = does not exist.', parameters: { type: 'object', properties: { key: { type: 'string', description: 'The Redis key name.' }, }, required: ['key'], }, }, }, { type: 'function', function: { name: 'redis_memory_usage', description: 'Run MEMORY USAGE to get bytes of memory a specific key uses (including overhead).', parameters: { type: 'object', properties: { key: { type: 'string', description: 'The Redis key name.' }, }, required: ['key'], }, }, }, { type: 'function', function: { name: 'redis_cluster_info', description: 'Run CLUSTER INFO to see cluster state (enabled, slot assignment, epoch, stats). Only meaningful in cluster mode.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_cluster_nodes', description: 'Run CLUSTER NODES to see cluster topology (masters, replicas, slot ranges). Only meaningful in cluster mode.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_acl_whoami', description: 'Run ACL WHOAMI to see which ACL user the connection is authenticated as.', parameters: { type: 'object', properties: {}, required: [] }, }, }, { type: 'function', function: { name: 'redis_module_list', description: 'Run MODULE LIST to enumerate loaded Redis modules (ReJSON, RediSearch, RedisTimeSeries, RedisBloom, etc.) with versions.', parameters: { type: 'object', properties: {}, required: [] }, }, }, ] const TOOL_EXECUTORS = { redis_info: async (redis, args) => { const section = typeof args.section === 'string' && args.section.length > 0 ? args.section : '' const reply = section ? await redis.call('INFO', section) : await redis.call('INFO') return formatReply(reply) }, redis_memory_stats: async (redis) => { const reply = await redis.call('MEMORY', 'STATS') return formatReply(reply) }, redis_slowlog_get: async (redis, args) => { const count = Math.min(Math.max(parseInt(args.count, 10) || 16, 1), 128) const reply = await redis.call('SLOWLOG', 'GET', count) return formatReply(reply) }, redis_client_list: async (redis) => { const reply = await redis.call('CLIENT', 'LIST') return formatReply(reply) }, redis_config_get: async (redis, args) => { const pattern = String(args.pattern || '*') const reply = await redis.call('CONFIG', 'GET', pattern) return formatReply(reply) }, redis_dbsize: async (redis) => { const reply = await redis.call('DBSIZE') return formatReply(reply) }, redis_latency_latest: async (redis) => { const reply = await redis.call('LATENCY', 'LATEST') return formatReply(reply) }, redis_scan: async (redis, args) => { const cursor = String(args.cursor ?? '0') const params = [cursor] if (typeof args.match === 'string' && args.match.length > 0) { params.push('MATCH', args.match) } const rawCount = parseInt(args.count, 10) const count = Number.isFinite(rawCount) ? Math.min(Math.max(rawCount, 1), 1000) : 100 params.push('COUNT', count) if (typeof args.type === 'string' && args.type.length > 0) { params.push('TYPE', args.type) } const reply = await redis.call('SCAN', ...params) return formatReply(reply) }, redis_type: async (redis, args) => { const key = String(args.key || '') if (!key) throw new Error('redis_type requires a key argument') const reply = await redis.call('TYPE', key) return formatReply(reply) }, redis_ttl: async (redis, args) => { const key = String(args.key || '') if (!key) throw new Error('redis_ttl requires a key argument') const reply = await redis.call('TTL', key) return formatReply(reply) }, redis_memory_usage: async (redis, args) => { const key = String(args.key || '') if (!key) throw new Error('redis_memory_usage requires a key argument') const reply = await redis.call('MEMORY', 'USAGE', key) return formatReply(reply) }, redis_cluster_info: async (redis) => { const reply = await redis.call('CLUSTER', 'INFO') return formatReply(reply) }, redis_cluster_nodes: async (redis) => { const reply = await redis.call('CLUSTER', 'NODES') return formatReply(reply) }, redis_acl_whoami: async (redis) => { const reply = await redis.call('ACL', 'WHOAMI') return formatReply(reply) }, redis_module_list: async (redis) => { const reply = await redis.call('MODULE', 'LIST') return formatReply(reply) }, } /** * Run a single tool call. Returns { result, ms, ok, error? }. * Never throws β€” any error is packaged into the result so the LLM can see and recover. */ export async function runTool(redis, name, args) { const t0 = Date.now() try { const fn = TOOL_EXECUTORS[name] if (!fn) { return { ok: false, error: `Unknown tool: ${name}`, ms: Date.now() - t0 } } const result = await fn(redis, args || {}) return { ok: true, result, ms: Date.now() - t0 } } catch (e) { return { ok: false, error: e.message || String(e), ms: Date.now() - t0 } } } src/service/socket.io/request/ai/validate-groq-api-key.mjs000066400000000000000000000017431517716222300241060ustar00rootroot00000000000000import Groq from 'groq-sdk' export default async (options) => { const { socket, payload } = options try { const apiKey = (payload.apiKey || '').trim() if (!apiKey) { socket.emit(options.responseEvent, { status: 'ok', valid: true }) return } if (!apiKey.startsWith('gsk_') || apiKey.length < 20) { socket.emit(options.responseEvent, { status: 'ok', valid: false, message: 'Invalid key format' }) return } const client = new Groq({ apiKey }) await client.chat.completions.create({ messages: [{ role: 'user', content: 'test' }], model: 'openai/gpt-oss-120b', max_tokens: 1, }) socket.emit(options.responseEvent, { status: 'ok', valid: true }) } catch (e) { console.error('validate-groq-api-key error', e.message) socket.emit(options.responseEvent, { status: 'ok', valid: false, message: e.message }) } } src/service/socket.io/request/client/000077500000000000000000000000001517716222300201575ustar00rootroot00000000000000src/service/socket.io/request/client/kill.mjs000066400000000000000000000010361517716222300216250ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket, payload} = options try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis await redis.client('KILL', 'ID', payload.id) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/client/list.mjs000066400000000000000000000031631517716222300216500ustar00rootroot00000000000000export default async (options) => { const {socket} = options try { const redis = socket.p3xrs.ioredis const raw = await redis.client('LIST') // Parse CLIENT LIST output (each line is a client, fields separated by spaces, key=value) const clients = [] for (const line of raw.split('\n')) { const trimmed = line.trim() if (!trimmed) continue const client = {} for (const pair of trimmed.split(' ')) { const eqIdx = pair.indexOf('=') if (eqIdx > 0) { client[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1) } } if (client.id) { clients.push({ id: client.id, addr: client.addr || '', name: client.name || '', age: parseInt(client.age) || 0, idle: parseInt(client.idle) || 0, db: parseInt(client.db) || 0, cmd: client.cmd || '', flags: client.flags || '', sub: parseInt(client.sub) || 0, psub: parseInt(client.psub) || 0, multi: parseInt(client.multi) || -1, omem: parseInt(client.omem) || 0, }) } } socket.emit(options.responseEvent, { status: 'ok', data: clients, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/cluster/000077500000000000000000000000001517716222300203625ustar00rootroot00000000000000src/service/socket.io/request/cluster/shards.mjs000066400000000000000000000060211517716222300223600ustar00rootroot00000000000000export default async (options) => { const { socket } = options try { const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } let shards try { // Redis 7.0+ β€” flat array shard data, needs parsing const raw = await redis.call('CLUSTER', 'SHARDS') shards = parseClusterShards(raw) } catch { // Fallback to CLUSTER SLOTS (Redis 3.0+) const slots = await redis.call('CLUSTER', 'SLOTS') shards = parseClusterSlots(slots) } socket.emit(options.responseEvent, { status: 'ok', data: { shards } }) } catch (e) { console.error('cluster/shards failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } // Parse flat array from CLUSTER SHARDS (Redis 7+) // Each shard: ["slots", [start, end], "nodes", [[flat node...], ...]] function parseClusterShards(raw) { return raw.map(entry => { const map = {} for (let i = 0; i < entry.length; i += 2) { map[entry[i]] = entry[i + 1] } const slotArr = map.slots || [] const slotRanges = [] for (let i = 0; i < slotArr.length; i += 2) { slotRanges.push([slotArr[i], slotArr[i + 1]]) } const nodes = (map.nodes || []).map(nodeArr => { const node = {} for (let i = 0; i < nodeArr.length; i += 2) { node[nodeArr[i]] = nodeArr[i + 1] } return { host: node.ip || node.endpoint, port: node.port, id: node.id, role: node.role } }) const master = nodes.find(n => n.role === 'master') || nodes[0] || { host: '?', port: 0, id: '?' } const replicas = nodes.filter(n => n.role !== 'master') return { slotRanges, master, replicas } }) } function parseClusterSlots(slots) { // CLUSTER SLOTS returns: [[startSlot, endSlot, [masterIP, port, id], [replicaIP, port, id], ...], ...] const shardMap = new Map() for (const entry of slots) { const start = entry[0] const end = entry[1] const masterInfo = entry[2] const masterId = masterInfo[2] || `${masterInfo[0]}:${masterInfo[1]}` if (!shardMap.has(masterId)) { shardMap.set(masterId, { slotRanges: [], master: { host: masterInfo[0], port: masterInfo[1], id: masterId }, replicas: [], }) } const shard = shardMap.get(masterId) shard.slotRanges.push([start, end]) // Replicas are entries[3], entries[4], etc. for (let i = 3; i < entry.length; i++) { const rep = entry[i] const repId = rep[2] || `${rep[0]}:${rep[1]}` if (!shard.replicas.find(r => r.id === repId)) { shard.replicas.push({ host: rep[0], port: rep[1], id: repId }) } } } return Array.from(shardMap.values()) } src/service/socket.io/request/cluster/slot-stats.mjs000066400000000000000000000025171517716222300232170ustar00rootroot00000000000000/** * CLUSTER SLOT-STATS β€” per-slot usage metrics (Redis 8.2+, cluster only) * Returns top slots by key count, CPU, or memory. */ export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { metric = 'KEY-COUNT', limit = 20, order = 'DESC' } = payload const raw = await redis.call('CLUSTER', 'SLOT-STATS', 'ORDERBY', metric, order, 'LIMIT', limit) // Parse flat array of [slot, [metric, value, ...], ...] const slots = [] if (Array.isArray(raw)) { for (const entry of raw) { if (Array.isArray(entry) && entry.length >= 2) { const slotNum = entry[0] const metrics = {} if (Array.isArray(entry[1])) { for (let i = 0; i < entry[1].length; i += 2) { metrics[entry[1][i]] = parseInt(entry[1][i + 1]) || 0 } } slots.push({ slot: slotNum, ...metrics }) } } } socket.emit(options.responseEvent, { status: 'ok', slots, }) } catch (e) { socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/connection/000077500000000000000000000000001517716222300210405ustar00rootroot00000000000000src/service/socket.io/request/connection/connect.mjs000066400000000000000000000353641517716222300232170ustar00rootroot00000000000000import Redis from '../../../../lib/ioredis-cluster/index.mjs' import * as sharedIoRedis from '../../shared.mjs' import staticCommands from '../../../../lib/redis-static-commands.mjs' import { buildCommandMeta } from '../../../../lib/redis-command-meta.mjs' const consolePrefix = 'socket.io connection-connect'; const generateConnectInfo = async (options) => { const {socket, redis, payload} = options const { db} = payload // console.warn('generateConnectInfo', options.payload) let databases //let results let commands = staticCommands const probeDatabaseCount = async() => { let tryUntilSelectDatabaseIsNotOk = true let currentDb = 0 let totalDb = 0 let maxDb = 512 await new Promise(resolve => setTimeout(resolve, 1000)); while(tryUntilSelectDatabaseIsNotOk) { try { await redis.call('select', currentDb) //console.info('found correct database index', currentDb) if (currentDb > maxDb) { console.warn(`limiting to max ${maxDb} database index, as it could crash with a big db index number`) tryUntilSelectDatabaseIsNotOk = false } currentDb++ } catch(e) { console.error(e); console.warn('found wrong current db index', currentDb) tryUntilSelectDatabaseIsNotOk = false } } totalDb = currentDb if (db <= totalDb) { try { await redis.call('select', db) } catch(e) { console.error(e) } } console.log('calculated max databases index', totalDb) return totalDb } if (options.payload.connection.cluster === true) { databases = 1 //commands = await redis.command() } else { try { databases = (await redis.config('get', 'databases'))[1] console.info(options.payload.connection.name, 'instance successfully works the database listing') } catch(e) { console.warn(options.payload.connection.name, 'instance get databases listing is disabled', e) databases = await probeDatabaseCount() } } console.info(options.payload.connection.name, 'databases got', databases) try { //commands = await redis.call('command2') commands = await redis.command() console.info(options.payload.connection.name, 'instance command listing is available') // , JSON.stringify(commands)) } catch(e) { console.warn(options.payload.connection.name, 'instance command listing is not available, not all redis instances are not available command listing', e) } // Detect loaded Redis modules (e.g. ReJSON, RediSearch, RedisTimeSeries) const modules = await sharedIoRedis.detectModules(redis) if (modules.length > 0) { console.info(options.payload.connection.name, 'modules detected:', modules.map(m => m.name).join(', ')) } await sharedIoRedis.getFullInfoAndSendSocket({ setDb: true, redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { databases: databases, commands: commands, commandsMeta: buildCommandMeta(commands), modules: modules, }, payload: payload, }) } export default async (options) => { const {socket, payload} = options; const {connection, db} = payload try { if (socket.p3xrs.connectionId !== connection.id) { sharedIoRedis.disconnectRedis({ socket: socket, }) } // Clean up stale ioredis connection if it exists but is no longer ready if (socket.p3xrs.ioredis !== undefined && socket.p3xrs.ioredis.status !== 'ready') { console.warn(consolePrefix, 'stale redis connection detected (status:', socket.p3xrs.ioredis.status + '), cleaning up before reconnect') sharedIoRedis.disconnectRedis({ socket: socket, }) } if (!p3xrs.redisConnections.hasOwnProperty(connection.id)) { p3xrs.redisConnections[connection.id] = { connection: connection, clients: [] } } if (!p3xrs.redisConnections[connection.id].clients.includes(socket.id)) { console.info(consolePrefix, 'added new socket.id', socket.id, 'to', connection.id, 'name with', connection.name) p3xrs.redisConnections[connection.id].clients.push(socket.id) } if (socket.p3xrs.ioredis !== undefined) { console.info(consolePrefix, 'redis was already connected') socket.p3xrs.connectionId = connection.id await generateConnectInfo({ redis: socket.p3xrs.ioredis, socket: socket, responseEvent: options.responseEvent, payload: payload }) sharedIoRedis.sendStatus({ socket: socket, }) } else { const actualConnection = p3xrs.connections.list.find(con => options.payload.connection.id === con.id) if (actualConnection === undefined) { throw new Error('auto-connection-failed') } if (connection.askAuth) { actualConnection.username = undefined actualConnection.password = undefined if (connection.username) { actualConnection.username = connection.username } if (connection.password) { actualConnection.password = connection.password } } let redisConfig = Object.assign({}, actualConnection); const sentinelName = redisConfig.sentinelName delete redisConfig.name delete redisConfig.id redisConfig.retryStrategy = null // module.exports = class Cluster extends Redis.Cluster <- right as it says redisConfig.clusterRetryStrategy = null /* redisConfig.showFriendlyErrorStack = true if (db !== undefined) { redisConfig.db = db } */ if (redisConfig.tlsWithoutCert) { redisConfig.tls = { servername: redisConfig.host } } else if (typeof redisConfig.tlsCa === 'string' && redisConfig.tlsCa.trim() !== '') { redisConfig.tls = { //rejectUnauthorized: false, cert: redisConfig.tlsCrt, key: redisConfig.tlsKey, ca: redisConfig.tlsCa, servername: redisConfig.host } } if (redisConfig.hasOwnProperty('tls')) { redisConfig.tls.rejectUnauthorized = redisConfig.tlsRejectUnauthorized === undefined ? false : redisConfig.tlsRejectUnauthorized // Ensure SNI is always set to the host if (!redisConfig.tls.hasOwnProperty('servername')) { redisConfig.tls.servername = redisConfig.host } } const closeRedis = () => { sharedIoRedis.disconnectRedis({ socket: socket, }) socket.p3xrs.connectionId = undefined socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined } // SSH tunnel creation - single SSH connection, multiple port forwards let connectionNodes = actualConnection.nodes || [] if (redisConfig.ssh === true) { const { createTunnel } = await import('tunnel-ssh') const net = await import('net') const sshOptions = { host: redisConfig.sshHost, port: redisConfig.sshPort, username: redisConfig.sshUsername, }; if (redisConfig.sshPrivateKey) { sshOptions.privateKey = redisConfig.sshPrivateKey } else { sshOptions.password = redisConfig.sshPassword } const tunnelServers = [] // Create primary tunnel (establishes the single SSH connection) let [primaryServer, sshClient] = await createTunnel({ autoClose: true }, null, sshOptions, { dstAddr: redisConfig.host, dstPort: redisConfig.port, }); tunnelServers.push(primaryServer) redisConfig.port = primaryServer.address().port // Create port forwards for additional nodes through the same SSH connection if (connectionNodes.length > 0) { connectionNodes = connectionNodes.map(node => Object.assign({}, node)) for (const node of connectionNodes) { const nodeServer = await new Promise((resolve, reject) => { const server = net.createServer((sock) => { sshClient.forwardOut('127.0.0.1', 0, node.host || 'localhost', node.port, (err, channel) => { if (err) { sock.end() return } sock.pipe(channel).pipe(sock) }) }) server.listen(0, '127.0.0.1', () => resolve(server)) server.on('error', reject) }) tunnelServers.push(nodeServer) node.port = nodeServer.address().port } } socket.p3xrs.tunnels = tunnelServers socket.p3xrs.sshClient = sshClient // Error handlers sshClient.on('error', async(e)=>{ console.error('ssh client error', e); closeRedis() socket.emit(options.responseEvent, { status: 'error', error: e.message }) }); for (const server of tunnelServers) { server.on('error', async(e)=>{ console.error('ssh tunnel server error', e); closeRedis() socket.emit(options.responseEvent, { status: 'error', error: e.message }) }); } } if (redisConfig.hasOwnProperty('sentinel') && redisConfig.sentinel === true) { redisConfig = [redisConfig].concat(connectionNodes) } else if (redisConfig.cluster === true) { redisConfig = [redisConfig].concat(connectionNodes) } if (Array.isArray(redisConfig) && redisConfig[0].hasOwnProperty('sentinel') && redisConfig[0].sentinel === true) { redisConfig = { sentinels: redisConfig, name: sentinelName, sentinelPassword: redisConfig[0].password, sentinelRetryStrategy: () => { return false } } } let redis = new Redis(redisConfig) //console.warn('redis connection', redisConfig) let redisSubscriber = new Redis(redisConfig) // let redis = await new Redis(redisConfig, {autoDetectCluster: true}) // let redisSubscriber = await new Redis(redisConfig, {autoDetectCluster: true}) socket.p3xrs.connectionId = connection.id socket.p3xrs.readonly = actualConnection.readonly === true socket.p3xrs.ioredis = redis socket.p3xrs.ioredisSubscriber = redisSubscriber let didConnected = false const redisErrorFun = async function (error) { if (!error) { error = new Error('Connection is closed.') error.p3xCode = 'disconnect' } const consolePrefix = 'socket.io connection-connect redis error fun' console.warn(consolePrefix, connection.id, connection.name, 'error') console.error(error) console.warn(consolePrefix, 'didConnected', didConnected) if (!didConnected) { socket.emit(options.responseEvent, { status: 'error', error: error.message }) } const disconnectedData = { connectionId: socket.p3xrs.connectionId, error: error.message, status: 'error', } console.warn(consolePrefix, 'disconnectedData', disconnectedData) socket.p3xrs.io.emit('redis-disconnected', disconnectedData) try { await sharedIoRedis.disconnectRedis({ socket: socket, }) } catch (e) { console.warn(consolePrefix, 'disconnectRedis') console.error(e) } closeRedis() sharedIoRedis.sendStatus({ socket: socket, }) } redis.on('error', redisErrorFun) redis.on('disconnect', redisErrorFun) redisSubscriber.on('error', redisErrorFun) redis.on('connect', async function () { try { console.info(consolePrefix, options.payload.connection.id, options.payload.connection.name, 'connected') didConnected = true await generateConnectInfo({ redis: redis, socket: socket, responseEvent: options.responseEvent, payload: options.payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } }) } } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/connection/delete.mjs000066400000000000000000000030021517716222300230100ustar00rootroot00000000000000import fs from 'fs' import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket} = options; const connectionSaveId = options.payload.id; let connectionIndexExisting; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false for (let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSaveId) { connectionIndexExisting = connectionIndex break; } } if (connectionIndexExisting !== undefined) { p3xrs.connections.list.splice(connectionIndexExisting, 1) p3xrs.connections.update = new Date() fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSaveId, code: 'delete-connection', socket: socket, }) } } } src/service/socket.io/request/connection/disconnect.mjs000066400000000000000000000016251517716222300237100ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io connection disconnect' export default async (options) => { const {socket, payload} = options; const {connectionId} = payload; console.warn(consolePrefix, 'connectionId', connectionId, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) try { if (socket.p3xrs.connectionId === connectionId) { console.warn(consolePrefix, 'will disconnect from redis') sharedIoRedis.disconnectRedis({ socket: socket, }) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } } src/service/socket.io/request/connection/reorder.mjs000066400000000000000000000052121517716222300232150ustar00rootroot00000000000000import fs from 'fs' import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket} = options; try { sharedIoRedis.ensureReadonlyConnections() const { group, ids } = options.payload; if (!Array.isArray(ids) || ids.length === 0) { socket.emit(options.responseEvent, { status: 'ok' }) return } // Build a lookup of current connections by id const byId = new Map() for (const conn of p3xrs.connections.list) { byId.set(conn.id, conn) } if (group !== undefined) { // Reorder within a specific group const reordered = [] for (const id of ids) { const conn = byId.get(id) if (conn) { reordered.push(conn) } } // Rebuild the full list preserving the relative position of groups const newList = [] let groupInserted = false const targetGroup = (group || '').trim() for (const conn of p3xrs.connections.list) { const connGroup = (conn.group || '').trim() if (connGroup === targetGroup) { if (!groupInserted) { newList.push(...reordered) groupInserted = true } } else { newList.push(conn) } } if (!groupInserted) { newList.push(...reordered) } p3xrs.connections.list = newList } else { // Full reorder (e.g. dragging groups) β€” ids contains all connection ids in new order const newList = [] for (const id of ids) { const conn = byId.get(id) if (conn) { newList.push(conn) } } // Append any connections not in the ids list (safety) for (const conn of p3xrs.connections.list) { if (!ids.includes(conn.id)) { newList.push(conn) } } p3xrs.connections.list = newList } p3xrs.connections.update = new Date() fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) socket.emit(options.responseEvent, { status: 'ok' }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { sharedIoRedis.sendConnections({ socket }) } } src/service/socket.io/request/connection/save.mjs000066400000000000000000000070731517716222300225200ustar00rootroot00000000000000import fs from 'fs' import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket} = options; const connectionSave = options.payload.model; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false let connectionIndexExisting; for (let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSave.id) { connectionIndexExisting = connectionIndex break; } } p3xrs.connections.update = new Date() if (connectionIndexExisting !== undefined) { if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.password) { connectionSave.password = p3xrs.connections.list[connectionIndexExisting].password; } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.tlsCrt) { connectionSave.tlsCrt = p3xrs.connections.list[connectionIndexExisting].tlsCrt; } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.tlsKey) { connectionSave.tlsKey = p3xrs.connections.list[connectionIndexExisting].tlsKey; } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.tlsCa) { connectionSave.tlsCa = p3xrs.connections.list[connectionIndexExisting].tlsCa; } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.tlsCa) { connectionSave.tlsCa = p3xrs.connections.list[connectionIndexExisting].tlsCa; } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.sshPassword) { connectionSave.sshPassword = p3xrs.connections.list[connectionIndexExisting].sshPassword } if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.sshPrivateKey) { connectionSave.sshPrivateKey = p3xrs.connections.list[connectionIndexExisting].sshPrivateKey } //TODO fix secured nodes password if (Array.isArray(connectionSave.nodes)) { for (let node of connectionSave.nodes) { const findNode = p3xrs.connections.list[connectionIndexExisting].nodes.find((findNode) => { return findNode.id === node.id && node.password === findNode.id }) if (findNode !== undefined) { node.password = findNode.password } } } p3xrs.connections.list[connectionIndexExisting] = connectionSave } else { p3xrs.connections.list.push(connectionSave) } fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSave.id, code: 'save-connection', socket: socket, }) } } } src/service/socket.io/request/connection/test.mjs000066400000000000000000000203651517716222300225400ustar00rootroot00000000000000import Redis from '../../../../lib/ioredis-cluster/index.mjs' export default async (options) => { const {socket} = options; try { let redisConfig = options.payload.model; const actualConnection = p3xrs.connections.list.find(con => redisConfig.id === con.id) if (actualConnection !== undefined) { if (redisConfig.password === actualConnection.id) { redisConfig.password = actualConnection.password; } if (redisConfig.tlsCrt === actualConnection.id) { redisConfig.tlsCrt = actualConnection.tlsCrt; } if (redisConfig.tlsKey === actualConnection.id) { redisConfig.tlsKey = actualConnection.tlsKey; } if (redisConfig.tlsCa === actualConnection.id) { redisConfig.tlsCa = actualConnection.tlsCa; } if (redisConfig.sshPassword === actualConnection.id) { redisConfig.sshPassword = actualConnection.sshPassword; } if (redisConfig.sshPrivateKey === actualConnection.id) { redisConfig.sshPrivateKey = actualConnection.sshPrivateKey; } } const sentinelName = redisConfig.sentinelName //TODO fix secured nodes password delete redisConfig.name delete redisConfig.id if (redisConfig.tlsWithoutCert) { redisConfig.tls = { servername: redisConfig.host } } else if (typeof redisConfig.tlsCa === 'string' && redisConfig.tlsCa.trim() !== '') { redisConfig.tls = { //rejectUnauthorized: false, cert: redisConfig.tlsCrt, key: redisConfig.tlsKey, ca: redisConfig.tlsCa, servername: redisConfig.host } } if (redisConfig.hasOwnProperty('tls')) { redisConfig.tls.rejectUnauthorized = redisConfig.tlsRejectUnauthorized === undefined ? false : redisConfig.tlsRejectUnauthorized // Ensure SNI is always set to the host if (!redisConfig.tls.hasOwnProperty('servername')) { redisConfig.tls.servername = redisConfig.host } } // Fix node passwords if (Array.isArray(redisConfig.nodes)) { redisConfig.nodes = redisConfig.nodes.map((node) => { if (actualConnection !== undefined && node.password === node.id) { const foundNode = actualConnection.nodes.find((findNode) => findNode.id === node.password) if (foundNode) { node.password = foundNode.password } } return node }) } // SSH tunnel creation - single SSH connection, multiple port forwards let sshTunnelServers = [] let sshClient = undefined let redis = undefined let settled = false let didReady = false let lastRedisError = undefined let timeout = undefined const closeSshTunnels = () => { for (const server of sshTunnelServers) { server.close() } sshTunnelServers = [] if (sshClient) { sshClient.end() sshClient = undefined } } const settle = (payload) => { if (settled) { return } settled = true if (timeout) { clearTimeout(timeout) timeout = undefined } socket.emit(options.responseEvent, payload) if (redis) { redis.disconnect() redis = undefined } closeSshTunnels() } if (redisConfig.ssh === true) { const { createTunnel } = await import('tunnel-ssh') const net = await import('net') const sshOptions = { host: redisConfig.sshHost, port: redisConfig.sshPort, username: redisConfig.sshUsername, }; if (redisConfig.sshPrivateKey) { sshOptions.privateKey = redisConfig.sshPrivateKey } else { sshOptions.password = redisConfig.sshPassword } // Create primary tunnel (establishes the single SSH connection) let [primaryServer, sshConn] = await createTunnel({ autoClose: false }, null, sshOptions, { dstAddr: redisConfig.host, dstPort: redisConfig.port, }); sshTunnelServers.push(primaryServer) sshClient = sshConn redisConfig.port = primaryServer.address().port // Create port forwards for additional nodes through the same SSH connection if (Array.isArray(redisConfig.nodes)) { for (const node of redisConfig.nodes) { const nodeServer = await new Promise((resolve, reject) => { const server = net.createServer((sock) => { sshClient.forwardOut('127.0.0.1', 0, node.host || 'localhost', node.port, (err, channel) => { if (err) { sock.end() return } sock.pipe(channel).pipe(sock) }) }) server.listen(0, '127.0.0.1', () => resolve(server)) server.on('error', reject) }) sshTunnelServers.push(nodeServer) node.port = nodeServer.address().port } } // Error handlers sshClient.on('error', (e)=>{ console.error('ssh client error', e); settle({ status: 'error', error: e.message }) }); for (const server of sshTunnelServers) { server.on('error', (e)=>{ console.error('ssh tunnel server error', e); settle({ status: 'error', error: e.message }) }); } } // Cluster/sentinel conversion if (redisConfig.hasOwnProperty('sentinel') && redisConfig.sentinel === true) { redisConfig = [redisConfig].concat(redisConfig.nodes || []) } else if (redisConfig.cluster === true) { redisConfig = [redisConfig].concat(redisConfig.nodes || []) } if (Array.isArray(redisConfig) && redisConfig[0].hasOwnProperty('sentinel') && redisConfig[0].sentinel === true) { redisConfig = { sentinels: redisConfig, name: sentinelName, sentinelPassword: redisConfig[0].password, sentinelRetryStrategy: () => { return false } } } redis = new Redis(redisConfig) //console.info('redis-test-connection', redisConfig) redis.on('error', function (error) { lastRedisError = error console.error(error) }) redis.on('ready', function () { didReady = true settle({ status: 'ok', }) }) redis.on('close', function () { if (!didReady) { settle({ status: 'error', error: lastRedisError?.message || 'Connection is closed.' }) } }) redis.on('end', function () { if (!didReady) { settle({ status: 'error', error: lastRedisError?.message || 'Connection is closed.' }) } }) timeout = setTimeout(() => { settle({ status: 'error', error: lastRedisError?.message || 'No response from server' }) }, 30000) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/connection/trigger-disconnect.mjs000066400000000000000000000011421517716222300253430ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io trigger redis disconnect' export default async (options) => { const {socket} = options; try { console.warn(consolePrefix, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) sharedIoRedis.disconnectRedis({ socket: socket, }) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/hash-field/000077500000000000000000000000001517716222300207055ustar00rootroot00000000000000src/service/socket.io/request/hash-field/ttl-get.mjs000066400000000000000000000011441517716222300230000ustar00rootroot00000000000000const consolePrefix = 'socket.io hash-field-ttl-get' export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { key, field } = payload const result = await redis.call('HTTL', key, 'FIELDS', 1, field) const ttl = Array.isArray(result) ? parseInt(result[0]) : -1 socket.emit(options.responseEvent, { status: 'ok', ttl: ttl, }) } catch (e) { socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/hash-field/ttl.mjs000066400000000000000000000016631517716222300222310ustar00rootroot00000000000000const consolePrefix = 'socket.io hash-field-ttl' export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { key, field, ttl } = payload if (ttl === -1) { // Remove field TTL (persist) await redis.call('HPERSIST', key, 'FIELDS', 1, field) } else { // Set field TTL in seconds await redis.call('HEXPIRE', key, ttl, 'FIELDS', 1, field) } // Get remaining TTL for the field const remaining = await redis.call('HTTL', key, 'FIELDS', 1, field) const fieldTtl = Array.isArray(remaining) ? parseInt(remaining[0]) : -1 socket.emit(options.responseEvent, { status: 'ok', ttl: fieldTtl, }) } catch (e) { socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/hash-field/ttls.mjs000066400000000000000000000021461517716222300224110ustar00rootroot00000000000000/** * Get TTL for all fields in a hash key (Redis 8.0+ HTTL command). * Returns { fieldTtls: { fieldName: ttlSeconds, ... } } */ export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { key, fields } = payload if (!fields || fields.length === 0) { socket.emit(options.responseEvent, { status: 'ok', fieldTtls: {} }) return } // HTTL key FIELDS count field [field ...] const result = await redis.call('HTTL', key, 'FIELDS', fields.length, ...fields) const fieldTtls = {} if (Array.isArray(result)) { for (let i = 0; i < fields.length && i < result.length; i++) { fieldTtls[fields[i]] = parseInt(result[i]) } } socket.emit(options.responseEvent, { status: 'ok', fieldTtls, }) } catch (e) { // Redis < 8.0 doesn't support HTTL β€” return empty socket.emit(options.responseEvent, { status: 'ok', fieldTtls: {}, }) } } src/service/socket.io/request/key/000077500000000000000000000000001517716222300174715ustar00rootroot00000000000000src/service/socket.io/request/key/del-tree.mjs000066400000000000000000000024041517716222300217050ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io key del tree' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis const deleteTree = `${payload.key}${payload.redisTreeDivider}*`; console.info(consolePrefix, deleteTree) const keys = await sharedIoRedis.getStreamKeys({ redis: redis, match: deleteTree, maxKeys: payload.maxKeys, }) const pipelineDeleteTree = redis.pipeline() for (let key of keys) { console.info(consolePrefix, 'delete key ', key) pipelineDeleteTree.del(key) } await pipelineDeleteTree.exec(); socket.emit(options.responseEvent, { status: 'ok', }) /* await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) */ } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/delete-search-keys.mjs000066400000000000000000000032551517716222300236670ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io delete search keys' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.match) if (!payload.match || payload.match === '*') { // No search filter: use flushdb for efficiency const dbsize = await redis.dbsize() console.info(consolePrefix, 'flushdb, dbsize was', dbsize) await redis.flushdb() socket.emit(options.responseEvent, { status: 'ok', deletedCount: dbsize, }) } else { const keys = await sharedIoRedis.getStreamKeys({ redis: redis, match: payload.match, maxKeys: payload.maxKeys, }) if (keys.length === 0) { socket.emit(options.responseEvent, { status: 'ok', deletedCount: 0, }) return } const pipeline = redis.pipeline() for (let key of keys) { console.info(consolePrefix, 'delete key', key) pipeline.del(key) } await pipeline.exec() socket.emit(options.responseEvent, { status: 'ok', deletedCount: keys.length, }) } } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/delete.mjs000066400000000000000000000015141517716222300214470ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io del key' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.del(payload.key) socket.emit(options.responseEvent, { status: 'ok', }) /* await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) */ } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/expire.mjs000066400000000000000000000012241517716222300214770ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io expire' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key, payload.ttl) await redis.expire(payload.key, parseInt(payload.ttl)) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/export.mjs000066400000000000000000000112471517716222300215320ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket, payload} = options try { const redis = socket.p3xrs.ioredis const keys = payload.keys if (!Array.isArray(keys) || keys.length === 0) { socket.emit(options.responseEvent, { status: 'error', error: 'No keys specified for export', }) return } // Get types for all keys const typePipeline = redis.pipeline() for (const key of keys) { typePipeline.type(key) } const typeResults = await typePipeline.exec() // Get TTLs for all keys const ttlPipeline = redis.pipeline() for (const key of keys) { ttlPipeline.pttl(key) } const ttlResults = await ttlPipeline.exec() // Read values based on type const exportedKeys = [] for (let i = 0; i < keys.length; i++) { const key = keys[i] let type = typeResults[i][1] const pttl = ttlResults[i][1] // Normalize ReJSON-RL if (type === 'ReJSON-RL') { type = 'json' } if (type === 'none') { continue } let value try { switch (type) { case 'string': { const buf = await redis.getBuffer(key) value = buf ? buf.toString('base64') : null break } case 'list': { const items = await redis.lrangeBuffer(key, 0, -1) value = items.map(item => item.toString('base64')) break } case 'set': { const members = await redis.smembersBuffer(key) value = members.map(m => m.toString('base64')) break } case 'zset': { // Returns [member, score, member, score, ...] const raw = await redis.zrangebyscoreBuffer(key, '-inf', '+inf', 'WITHSCORES') const entries = [] for (let j = 0; j < raw.length; j += 2) { entries.push({ member: raw[j].toString('base64'), score: parseFloat(raw[j + 1].toString()), }) } value = entries break } case 'hash': { const raw = await redis.hgetallBuffer(key) const entries = {} if (raw) { for (const [field, val] of Object.entries(raw)) { entries[field] = val.toString('base64') } } value = entries break } case 'stream': { const entries = await redis.xrange(key, '-', '+') value = entries.map(([id, fields]) => { const obj = { id } for (let j = 0; j < fields.length; j += 2) { obj[fields[j]] = fields[j + 1] } return obj }) break } case 'json': { const jsonStr = await redis.call('JSON.GET', key) value = jsonStr break } default: continue } } catch (e) { console.error(`key-export: failed to read key "${key}" (type=${type}):`, e.message) continue } exportedKeys.push({ key, type, value, pttl: pttl > 0 ? pttl : -1, }) } socket.emit(options.responseEvent, { status: 'ok', data: { version: 1, exportedAt: new Date().toISOString(), database: socket.p3xrs.currentDatabase || 0, keys: exportedKeys, }, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/get-string-buffer.mjs000066400000000000000000000012421517716222300235350ustar00rootroot00000000000000const consolePrefix = 'socket.io key get string buffer' export default async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis const key = payload.key; const buffer = await redis.getBuffer(key) const socketResult = { key: key, status: 'ok', bufferValue: buffer, }; // console.warn('socketResult', socketResult) socket.emit(options.responseEvent, socketResult) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/get.mjs000066400000000000000000000302571517716222300207720ustar00rootroot00000000000000import { tryDecompress } from '../../../decompress.mjs' const consolePrefix = 'socket.io key get full' export default async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis const key = payload.key; //const type = payload.type; let type = await redis.type(key) // Normalize ReJSON-RL to json for the client. if (type === 'ReJSON-RL') { type = 'json' } // Normalize TSDB-TYPE to timeseries if (type === 'TSDB-TYPE') { type = 'timeseries' } // Normalize RedisBloom module types if (type === 'MBbloom--') type = 'bloom' if (type === 'MBbloomCF') type = 'cuckoo' if (type === 'TopK-TYPE') type = 'topk' if (type === 'CMSk-TYPE') type = 'cms' if (type === 'TDIS-TYPE') type = 'tdigest' //console.info(consolePrefix, payload, type, key) const viewPipeline = redis.pipeline() switch (type) { case 'string': //viewPipeline.get(key) viewPipeline.getBuffer(key) break; case 'list': //viewPipeline.lrange(key, 0, -1) viewPipeline.lrangeBuffer(key, 0, -1) break; case 'hash': //viewPipeline.hgetall(key) viewPipeline.hgetallBuffer(key) break; case 'set': //viewPipeline.smembers(key) viewPipeline.smembersBuffer(key) break; case 'zset': //viewPipeline.zrange(key, 0, -1, 'WITHSCORES') viewPipeline.zrangeBuffer(key, 0, -1, 'WITHSCORES') break; case 'stream': //viewPipeline.xrange(key, '-', '+') viewPipeline.xrangeBuffer(key, '-', '+') break; case 'json': viewPipeline.call('JSON.GET', key, '$') break; case 'timeseries': // TS.INFO via pipeline call viewPipeline.call('TS.INFO', key) break; case 'bloom': viewPipeline.call('BF.INFO', key) break; case 'cuckoo': viewPipeline.call('CF.INFO', key) break; case 'topk': viewPipeline.call('TOPK.INFO', key) break; case 'cms': viewPipeline.call('CMS.INFO', key) break; case 'tdigest': viewPipeline.call('TDIGEST.INFO', key) break; case 'vectorset': viewPipeline.call('VINFO', key) break; default: // Unknown type β€” send type name as placeholder value viewPipeline.type(key) break; } viewPipeline.ttl(key) // Only native collection types support OBJECT ENCODING const encodingTypes = ['string', 'stream', 'hash', 'list', 'set', 'zset'] if (encodingTypes.includes(type)) { viewPipeline.object('encoding', key) } switch (type) { case 'stream': viewPipeline.xlen(key) break; case 'hash': viewPipeline.hlen(key) break; case 'list': viewPipeline.llen(key) break; case 'set': viewPipeline.scard(key) break; case 'zset': viewPipeline.zcard(key) break; } const viewPipelineResult = await viewPipeline.exec() // console.log(viewPipelineResult) let valueBuffer = viewPipelineResult[0][1] const ttl = viewPipelineResult[1][1] let encoding let length let pipelineIndex = 2 const probabilisticTypes = ['bloom', 'cuckoo', 'topk', 'cms', 'tdigest'] // Types that have OBJECT ENCODING + length command in the pipeline const collectionTypes = ['stream', 'hash', 'list', 'set', 'zset'] if (type === 'timeseries') { encoding = 'timeseries' // TS.INFO returns flat array [field, value, ...]; parse to object const tsInfo = {} if (Array.isArray(valueBuffer)) { for (let i = 0; i < valueBuffer.length; i += 2) { const field = valueBuffer[i] let value = valueBuffer[i + 1] if (field === 'labels' && Array.isArray(value)) { const labels = {} for (const pair of value) { if (Array.isArray(pair) && pair.length === 2) { labels[pair[0]] = pair[1] } } value = labels } if (field === 'rules' && Array.isArray(value)) { value = value.map(rule => Array.isArray(rule) ? { destKey: rule[0], bucketDuration: rule[1], aggregationType: rule[2] } : rule) } tsInfo[field] = value } } valueBuffer = Buffer.from(JSON.stringify(tsInfo)) length = tsInfo.totalSamples || 0 } else if (probabilisticTypes.includes(type) || type === 'vectorset') { encoding = type // All probabilistic/vectorset INFO commands return flat array [field, value, ...] const info = {} if (Array.isArray(valueBuffer)) { for (let i = 0; i < valueBuffer.length; i += 2) { info[valueBuffer[i]] = valueBuffer[i + 1] } } valueBuffer = Buffer.from(JSON.stringify(info)) length = info['Number of items inserted'] || info['count'] || info['k'] || info['Merged nodes'] || 0 } else if (type === 'json') { encoding = 'json' // JSON.GET returns a JSON string; convert to Buffer for consistency if (typeof valueBuffer === 'string') { valueBuffer = Buffer.from(valueBuffer) } } else if (type === 'string') { // String has OBJECT ENCODING but no length command encoding = viewPipelineResult[pipelineIndex][1] pipelineIndex++ } else if (collectionTypes.includes(type)) { // Collections have OBJECT ENCODING + length command encoding = viewPipelineResult[pipelineIndex][1] pipelineIndex++ length = viewPipelineResult[pipelineIndex][1] } else { // Unknown module type β€” no OBJECT ENCODING, no length command encoding = type valueBuffer = Buffer.from(JSON.stringify({ type: valueBuffer || type })) length = 0 } // Try to decompress the value (string type: single buffer, collections: each item) let compression = null if (type === 'string' && Buffer.isBuffer(valueBuffer)) { const result = await tryDecompress(valueBuffer) if (result) { compression = { algorithm: result.algorithm, originalSize: valueBuffer.length, decompressedSize: result.decompressed.length, ratio: +((1 - valueBuffer.length / result.decompressed.length) * 100).toFixed(1), } valueBuffer = result.decompressed } } else if (type === 'list' && Array.isArray(valueBuffer)) { for (let i = 0; i < valueBuffer.length; i++) { if (Buffer.isBuffer(valueBuffer[i])) { const result = await tryDecompress(valueBuffer[i]) if (result) { if (!compression) { compression = { algorithm: result.algorithm, items: 0, originalSize: 0, decompressedSize: 0 } } compression.items++ compression.originalSize += valueBuffer[i].length compression.decompressedSize += result.decompressed.length valueBuffer[i] = result.decompressed } } } if (compression) { compression.ratio = +((1 - compression.originalSize / compression.decompressedSize) * 100).toFixed(1) } } else if (type === 'hash' && valueBuffer && typeof valueBuffer === 'object' && !Array.isArray(valueBuffer)) { for (const field of Object.keys(valueBuffer)) { if (Buffer.isBuffer(valueBuffer[field])) { const result = await tryDecompress(valueBuffer[field]) if (result) { if (!compression) { compression = { algorithm: result.algorithm, items: 0, originalSize: 0, decompressedSize: 0 } } compression.items++ compression.originalSize += valueBuffer[field].length compression.decompressedSize += result.decompressed.length valueBuffer[field] = result.decompressed } } } if (compression) { compression.ratio = +((1 - compression.originalSize / compression.decompressedSize) * 100).toFixed(1) } } else if ((type === 'set' || type === 'zset') && Array.isArray(valueBuffer)) { for (let i = 0; i < valueBuffer.length; i++) { if (Buffer.isBuffer(valueBuffer[i])) { const result = await tryDecompress(valueBuffer[i]) if (result) { if (!compression) { compression = { algorithm: result.algorithm, items: 0, originalSize: 0, decompressedSize: 0 } } compression.items++ compression.originalSize += valueBuffer[i].length compression.decompressedSize += result.decompressed.length valueBuffer[i] = result.decompressed } } } if (compression) { compression.ratio = +((1 - compression.originalSize / compression.decompressedSize) * 100).toFixed(1) } } else if (type === 'stream' && Array.isArray(valueBuffer)) { // Stream entries: [[id, [field, value, field, value]], ...] for (const entry of valueBuffer) { if (!Array.isArray(entry) || !Array.isArray(entry[1])) continue const fields = entry[1] for (let i = 0; i < fields.length; i++) { if (Buffer.isBuffer(fields[i])) { const result = await tryDecompress(fields[i]) if (result) { if (!compression) { compression = { algorithm: result.algorithm, items: 0, originalSize: 0, decompressedSize: 0 } } compression.items++ compression.originalSize += fields[i].length compression.decompressedSize += result.decompressed.length fields[i] = result.decompressed } } } } if (compression) { compression.ratio = +((1 - compression.originalSize / compression.decompressedSize) * 100).toFixed(1) } } const socketResult = { length: length, key: key, status: 'ok', type: type, valueBuffer: valueBuffer, ttl: ttl, encoding: encoding, compression: compression, }; // console.warn('socketResult', socketResult) socket.emit(options.responseEvent, socketResult) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/hash-delete-field.mjs000066400000000000000000000011571517716222300234540ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io key hash delete key' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {hashKey, key} = payload; await redis.hdel(key, hashKey) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/import.mjs000066400000000000000000000227611517716222300215260ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const BATCH_SIZE = 500 export default async (options) => { const {socket, payload} = options try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { keys, conflictMode } = payload // conflictMode: 'overwrite' | 'skip' if (!Array.isArray(keys) || keys.length === 0) { socket.emit(options.responseEvent, { status: 'error', error: 'No keys to import', }) return } // Check existing keys if skip mode (pipelined in batches) let existingKeys = new Set() if (conflictMode === 'skip') { for (let i = 0; i < keys.length; i += BATCH_SIZE) { const batch = keys.slice(i, i + BATCH_SIZE) const pipeline = redis.pipeline() for (const entry of batch) { pipeline.exists(entry.key) } const results = await pipeline.exec() for (let j = 0; j < batch.length; j++) { if (results[j][0]) { // exists check itself failed β€” treat as not existing continue } if (results[j][1] === 1) { existingKeys.add(batch[j].key) } } } } let created = 0 let skipped = 0 let errors = 0 // Process keys in batches for (let i = 0; i < keys.length; i += BATCH_SIZE) { const batch = keys.slice(i, i + BATCH_SIZE) // Filter out skipped keys const toImport = [] for (const entry of batch) { if (conflictMode === 'skip' && existingKeys.has(entry.key)) { skipped++ } else { toImport.push(entry) } } if (toImport.length === 0) continue // Separate pipelineable types from sequential (stream, json) const pipelineable = [] const sequential = [] for (const entry of toImport) { if (entry.type === 'stream' || entry.type === 'json') { sequential.push(entry) } else { pipelineable.push(entry) } } // --- Pipelineable keys (string, list, set, zset, hash) --- if (pipelineable.length > 0) { // Delete existing keys if overwriting if (conflictMode === 'overwrite') { const delPipeline = redis.pipeline() for (const entry of pipelineable) { delPipeline.del(entry.key) } await delPipeline.exec() } // Build write pipeline β€” track which entries map to which pipeline slot const writePipeline = redis.pipeline() const pipelineEntries = [] // entries that actually got a command for (const entry of pipelineable) { try { switch (entry.type) { case 'string': writePipeline.set(entry.key, Buffer.from(entry.value, 'base64')) pipelineEntries.push(entry) break case 'list': if (Array.isArray(entry.value) && entry.value.length > 0) { writePipeline.rpush(entry.key, ...entry.value.map(v => Buffer.from(v, 'base64'))) pipelineEntries.push(entry) } else { created++ // empty list, nothing to write } break case 'set': if (Array.isArray(entry.value) && entry.value.length > 0) { writePipeline.sadd(entry.key, ...entry.value.map(v => Buffer.from(v, 'base64'))) pipelineEntries.push(entry) } else { created++ } break case 'zset': if (Array.isArray(entry.value) && entry.value.length > 0) { const args = [] for (const e of entry.value) { args.push(e.score, Buffer.from(e.member, 'base64')) } writePipeline.zadd(entry.key, ...args) pipelineEntries.push(entry) } else { created++ } break case 'hash': if (entry.value && typeof entry.value === 'object') { const args = [] for (const [field, val] of Object.entries(entry.value)) { args.push(field, Buffer.from(val, 'base64')) } if (args.length > 0) { writePipeline.hset(entry.key, ...args) pipelineEntries.push(entry) } else { created++ } } break default: errors++ } } catch (e) { console.error(`key-import: failed to prepare key "${entry.key}":`, e.message) errors++ } } if (pipelineEntries.length > 0) { const writeResults = await writePipeline.exec() // Collect successful entries for TTL restore const successEntries = [] for (let j = 0; j < writeResults.length; j++) { if (writeResults[j][0]) { console.error(`key-import: write failed for key "${pipelineEntries[j].key}":`, writeResults[j][0].message) errors++ } else { created++ if (pipelineEntries[j].pttl && pipelineEntries[j].pttl > 0) { successEntries.push(pipelineEntries[j]) } } } // Pipeline TTL restore only for successfully written keys if (successEntries.length > 0) { const ttlPipeline = redis.pipeline() for (const entry of successEntries) { ttlPipeline.pexpire(entry.key, entry.pttl) } await ttlPipeline.exec() } } } // --- Sequential keys (stream, json) --- for (const entry of sequential) { try { if (conflictMode === 'overwrite') { await redis.del(entry.key) } if (entry.type === 'stream') { if (Array.isArray(entry.value) && entry.value.length > 0) { const streamPipeline = redis.pipeline() for (const streamEntry of entry.value) { const { id, ...fields } = streamEntry const args = [] for (const [k, v] of Object.entries(fields)) { args.push(k, v) } if (args.length > 0) { streamPipeline.xadd(entry.key, '*', ...args) } } await streamPipeline.exec() } } else if (entry.type === 'json') { if (entry.value !== null && entry.value !== undefined) { await redis.call('JSON.SET', entry.key, '$', typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value)) } } if (entry.pttl && entry.pttl > 0) { await redis.pexpire(entry.key, entry.pttl) } created++ } catch (e) { console.error(`key-import: failed to import key "${entry.key}" (type=${entry.type}):`, e.message) errors++ } } } socket.emit(options.responseEvent, { status: 'ok', data: { created, skipped, errors }, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/json-set.mjs000066400000000000000000000013301517716222300217430ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const { key, path, value } = payload if (!key) { throw new Error('key is required') } const jsonPath = path || '$' await redis.call('JSON.SET', key, jsonPath, value) socket.emit(options.responseEvent, { status: 'ok', key: key, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/list-delete-index.mjs000066400000000000000000000014771517716222300235350ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' import utils from 'corifeus-utils' const consolePrefix = 'socket.io key list delete index' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {index, key} = payload; const uniqueValue = utils.random.complexUuid() console.log(consolePrefix, key, index, uniqueValue) await redis.lset(key, index, uniqueValue) await redis.lrem(key, 1, uniqueValue) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/new-or-set.mjs000066400000000000000000000247551517716222300222210ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const isBinaryLike = (value) => { if (value === undefined || value === null) { return false } if (Buffer.isBuffer(value)) { return true } if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { return true } if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(value)) { return true } return false } const consolePrefix = 'socket.io key new' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {model} = payload; model.score = model.score === null ? undefined : model.score model.index = model.index === null ? undefined : model.index model.hashKey = model.hashKey === null ? undefined : model.hashKey //console.warn(consolePrefix, payload) switch (model.type) { case 'stream': const xaddArgs = [ model.key, model.streamTimestamp, ].concat(sharedIoRedis.argumentParser(model.value)) await redis.xadd(...xaddArgs) break; case 'string': await redis.set(model.key, model.value) break; case 'list': if (model.index === undefined) { await redis.rpush(model.key, model.value) } else { if (model.index === -1) { await redis.lpush(model.key, model.value) } else { const size = await redis.llen(model.key); if (model.index > -1 && model.index < size) { await redis.lset(model.key, model.index, model.value) } else { const listOutOBoundsError = new Error('list-out-of-bounds') listOutOBoundsError.code = 'list-out-of-bounds' throw listOutOBoundsError } } } break; case 'hash': if (payload.hasOwnProperty('originalHashKey')) { await redis.hdel(model.key, payload.originalHashKey) } await redis.hset(model.key, model.hashKey, model.value) break; case 'set': if (payload.hasOwnProperty('originalValue')) { await redis.srem(model.key, payload.originalValue) } await redis.sadd(model.key, model.value) break; case 'zset': if (payload.hasOwnProperty('originalValue')) { await redis.zrem(model.key, payload.originalValue) } await redis.zadd(model.key, model.score, model.value) break; case 'timeseries': if (payload.type === 'add' || payload.type === 'append') { // For new keys, create first with options if (payload.type === 'add') { try { const createArgs = [model.key, 'DUPLICATE_POLICY', model.tsDuplicatePolicy || 'LAST'] const retention = parseInt(model.tsRetention) if (!isNaN(retention) && retention > 0) { createArgs.push('RETENTION', retention) } await redis.call('TS.CREATE', ...createArgs) } catch (e) { if (!e.message.includes('already exists')) { throw e } } // Always set labels via TS.ALTER (works for both new and existing keys) const labelStr = model.tsLabels && model.tsLabels.trim().length > 0 ? model.tsLabels.trim() : `key ${model.key}` console.info('timeseries set labels:', model.key, labelStr) await redis.call('TS.ALTER', model.key, 'LABELS', ...labelStr.split(/\s+/)) } if (model.tsBulkMode) { // Bulk mode: value is multiline "timestamp value\n..." const spread = parseInt(model.tsSpread) || 60000 const lines = model.value.split('\n').map(l => l.trim()).filter(l => l.length > 0) let autoTs = Date.now() for (const line of lines) { const parts = line.split(/\s+/) if (parts.length >= 2) { let ts = parts[0] if (ts === '*') { ts = autoTs autoTs += spread } await redis.call('TS.ADD', model.key, ts, parseFloat(parts[1]), 'ON_DUPLICATE', 'LAST') } } } else { await redis.call('TS.ADD', model.key, model.tsTimestamp || '*', parseFloat(model.value), 'ON_DUPLICATE', 'LAST') } } else if (payload.type === 'edit') { if (model.tsEditAll) { // Global edit: value is multiline "timestamp value\n..." format // Delete all existing points first, then re-add from the edited text const tsInfo = await redis.call('TS.INFO', model.key) let firstTs = 0, lastTs = 0 for (let i = 0; i < tsInfo.length; i += 2) { if (tsInfo[i] === 'firstTimestamp') firstTs = tsInfo[i + 1] if (tsInfo[i] === 'lastTimestamp') lastTs = tsInfo[i + 1] } if (firstTs !== 0 || lastTs !== 0) { await redis.call('TS.DEL', model.key, firstTs, lastTs) } // Parse and re-add each line // For * timestamps, space them by the selected spread interval const spread = parseInt(model.tsSpread) || 60000 const lines = model.value.split('\n').map(l => l.trim()).filter(l => l.length > 0) let autoTs = Date.now() for (const line of lines) { const parts = line.split(/\s+/) if (parts.length >= 2) { let ts = parts[0] if (ts === '*') { ts = autoTs autoTs += spread } await redis.call('TS.ADD', model.key, ts, parseFloat(parts[1]), 'ON_DUPLICATE', 'LAST') } } } else { // Single point edit: delete original, add new if (model.originalTimestamp !== undefined) { await redis.call('TS.DEL', model.key, model.originalTimestamp, model.originalTimestamp) } await redis.call('TS.ADD', model.key, model.tsTimestamp || '*', parseFloat(model.value), 'ON_DUPLICATE', 'LAST') } // Update labels if provided if (model.tsLabels && model.tsLabels.trim().length > 0) { await redis.call('TS.ALTER', model.key, 'LABELS', ...model.tsLabels.trim().split(/\s+/)) } } break; case 'json': // Validate JSON before sending to Redis try { JSON.parse(model.value) } catch (e) { throw new Error('invalid-json-value') } await redis.call('JSON.SET', model.key, '$', model.value) break; case 'bloom': await redis.call('BF.RESERVE', model.key, parseFloat(model.bloomErrorRate) || 0.01, parseInt(model.bloomCapacity) || 100) break; case 'cuckoo': await redis.call('CF.RESERVE', model.key, parseInt(model.cuckooCapacity) || 1024) break; case 'topk': await redis.call('TOPK.RESERVE', model.key, parseInt(model.topkK) || 10, parseInt(model.topkWidth) || 2000, parseInt(model.topkDepth) || 7, parseFloat(model.topkDecay) || 0.9) break; case 'cms': await redis.call('CMS.INITBYDIM', model.key, parseInt(model.cmsWidth) || 2000, parseInt(model.cmsDepth) || 7) break; case 'tdigest': await redis.call('TDIGEST.CREATE', model.key, 'COMPRESSION', parseInt(model.tdigestCompression) || 100) break; case 'vectorset': { const values = (model.vectorValues || '').split(',').map(v => parseFloat(v.trim())).filter(v => !isNaN(v)) if (!values.length) throw new Error('Vector values are required') if (!model.vectorElement || !model.vectorElement.trim()) throw new Error('Element name is required') const args = [model.key, 'VALUES', values.length, ...values, model.vectorElement.trim()] await redis.call('VADD', ...args) break; } } socket.emit(options.responseEvent, { status: 'ok', key: model.key, }) /* await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { key: model.key }, payload: payload, }) */ } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/persist.mjs000066400000000000000000000010751517716222300217000ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io persists' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis await redis.persist(payload.key) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/rename.mjs000066400000000000000000000015441517716222300214570ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io rename key' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.rename(payload.key, payload.keyNew) /* await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) */ socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/set-delete-member.mjs000066400000000000000000000011631517716222300235050ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io key list delete index' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {key, value} = payload; await redis.sremBuffer(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/set.mjs000066400000000000000000000021031517716222300207730ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const isBinaryLike = (value) => { if (value === undefined || value === null) { return false } if (Buffer.isBuffer(value)) { return true } if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { return true } if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(value)) { return true } return false } export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const ttl = await redis.ttl(payload.key) await redis.set(payload.key, payload.value) if (ttl !== -1) { await redis.expire(payload.key, ttl) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/stream-delete-timestamp.mjs000066400000000000000000000012061517716222300247370ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io stream delete timestamp id' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {key, streamTimestamp} = payload; await redis.xdel(key, streamTimestamp) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key/string-digest.mjs000066400000000000000000000010071517716222300227650ustar00rootroot00000000000000const consolePrefix = 'socket.io string-digest' export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { key } = payload const digest = await redis.call('DIGEST', key) socket.emit(options.responseEvent, { status: 'ok', digest: digest, }) } catch (e) { socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key/zset-delete-member.mjs000066400000000000000000000011641517716222300237000ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io key zsit delete member' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) const {key, value} = payload; await redis.zremBuffer(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/memory/000077500000000000000000000000001517716222300202115ustar00rootroot00000000000000src/service/socket.io/request/memory/analysis.mjs000066400000000000000000000133251517716222300225530ustar00rootroot00000000000000export default async (options) => { const {socket, payload} = options try { const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } const maxScanKeys = payload.maxScanKeys || 5000 const topN = payload.topN || 20 const BATCH = 500 // Get total key count const dbSize = await redis.dbsize() // Scan keys (scanStream is cluster-aware, scans all masters) const keys = await new Promise((resolve, reject) => { const collected = [] const stream = redis.scanStream({ count: 500 }) stream.on('data', (batch) => { if (collected.length < maxScanKeys) { collected.push(...batch) } }) stream.on('end', () => resolve(collected.slice(0, maxScanKeys))) stream.on('error', reject) }) // Pipeline: TYPE + MEMORY USAGE + TTL for each key const typeDistribution = {} const typeMemory = {} const prefixBuckets = {} const allKeys = [] let withTTL = 0 let persistent = 0 let ttlSum = 0 for (let i = 0; i < keys.length; i += BATCH) { const batch = keys.slice(i, i + BATCH) const pipeline = redis.pipeline() for (const key of batch) { pipeline.type(key) pipeline.call('MEMORY', 'USAGE', key) pipeline.ttl(key) } const results = await pipeline.exec() for (let j = 0; j < batch.length; j++) { const key = batch[j] const typeErr = results[j * 3][0] const type = results[j * 3][1] || 'unknown' const memErr = results[j * 3 + 1][0] const bytes = results[j * 3 + 1][1] const ttlErr = results[j * 3 + 2][0] const ttl = results[j * 3 + 2][1] const keyType = !typeErr ? type : 'unknown' const keyBytes = (!memErr && typeof bytes === 'number') ? bytes : 0 // Type distribution typeDistribution[keyType] = (typeDistribution[keyType] || 0) + 1 typeMemory[keyType] = (typeMemory[keyType] || 0) + keyBytes // Prefix buckets (split by first : delimiter) const colonIdx = key.indexOf(':') const prefix = colonIdx > 0 ? key.substring(0, colonIdx + 1) : '(no prefix)' if (!prefixBuckets[prefix]) { prefixBuckets[prefix] = { keyCount: 0, totalBytes: 0 } } prefixBuckets[prefix].keyCount++ prefixBuckets[prefix].totalBytes += keyBytes // TTL if (!ttlErr && typeof ttl === 'number') { if (ttl >= 0) { withTTL++ ttlSum += ttl } else { persistent++ } } else { persistent++ } // Top keys if (keyBytes > 0) { allKeys.push({ key, bytes: keyBytes, type: keyType }) } } } // Sort top keys allKeys.sort((a, b) => b.bytes - a.bytes) const topKeys = allKeys.slice(0, topN) // Sort prefix buckets by memory const prefixMemory = Object.entries(prefixBuckets) .map(([prefix, data]) => ({ prefix, ...data })) .sort((a, b) => b.totalBytes - a.totalBytes) .slice(0, 50) // INFO server const serverInfoRaw = await redis.info('server') const serverInfo = {} const serverFields = { redis_version: 'version', redis_mode: 'mode', uptime_in_seconds: 'uptime', } for (const line of serverInfoRaw.split('\r\n')) { const [k, v] = line.split(':') if (k && serverFields[k]) { serverInfo[serverFields[k]] = k === 'uptime_in_seconds' ? (parseInt(v) || 0) : (v || 'unknown') } } if (!serverInfo.mode) serverInfo.mode = 'standalone' // INFO memory const memoryInfoRaw = await redis.info('memory') const memoryInfo = {} const fields = { used_memory: 'used', used_memory_human: 'usedHuman', used_memory_rss: 'rss', used_memory_rss_human: 'rssHuman', used_memory_peak: 'peak', used_memory_peak_human: 'peakHuman', used_memory_lua: 'lua', used_memory_overhead: 'overhead', used_memory_dataset: 'dataset', mem_fragmentation_ratio: 'fragRatio', mem_allocator: 'allocator', } for (const line of memoryInfoRaw.split('\r\n')) { const [k, v] = line.split(':') if (k && fields[k]) { memoryInfo[fields[k]] = isNaN(v) ? v : Number(v) } } socket.emit(options.responseEvent, { status: 'ok', data: { totalScanned: keys.length, dbSize, typeDistribution, typeMemory, prefixMemory, topKeys, expirationOverview: { withTTL, persistent, avgTTL: withTTL > 0 ? Math.round(ttlSum / withTTL) : 0, }, memoryInfo, serverInfo, }, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/memory/doctor.mjs000066400000000000000000000010761517716222300222220ustar00rootroot00000000000000export default async (options) => { const { socket } = options try { const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } const result = await redis.call('MEMORY', 'DOCTOR') socket.emit(options.responseEvent, { status: 'ok', data: { text: result } }) } catch (e) { console.error('memory/doctor failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/memory/top-keys.mjs000066400000000000000000000035451517716222300225060ustar00rootroot00000000000000export default async (options) => { const {socket, payload} = options try { const redis = socket.p3xrs.ioredis const maxKeys = payload.maxKeys || 100 const topN = payload.topN || 20 // Scan keys (scanStream is cluster-aware, scans all masters) const keys = await new Promise((resolve, reject) => { const collected = [] const stream = redis.scanStream({ count: 200 }) stream.on('data', (batch) => { if (collected.length < maxKeys) { collected.push(...batch) } }) stream.on('end', () => resolve(collected.slice(0, maxKeys))) stream.on('error', reject) }) // Get memory usage for each key in pipeline batches const BATCH = 500 const results = [] for (let i = 0; i < keys.length; i += BATCH) { const batch = keys.slice(i, i + BATCH) const pipeline = redis.pipeline() for (const key of batch) { pipeline.call('MEMORY', 'USAGE', key) } const pipeResults = await pipeline.exec() for (let j = 0; j < batch.length; j++) { const err = pipeResults[j][0] const bytes = pipeResults[j][1] if (!err && typeof bytes === 'number') { results.push({ key: batch[j], bytes }) } } } // Sort by size descending and take top N results.sort((a, b) => b.bytes - a.bytes) const topKeys = results.slice(0, topN) socket.emit(options.responseEvent, { status: 'ok', data: topKeys, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/monitor/000077500000000000000000000000001517716222300203705ustar00rootroot00000000000000src/service/socket.io/request/monitor/info.mjs000066400000000000000000000074471517716222300220520ustar00rootroot00000000000000export default async (options) => { const {socket} = options try { const redis = socket.p3xrs.ioredis const [infoRaw, slowlog] = await Promise.all([ redis.info(), redis.slowlog('GET', 10), ]) // Parse INFO into sections const info = {} let currentSection = '' for (const line of infoRaw.split('\n')) { const trimmed = line.trim() if (!trimmed || trimmed.startsWith('#')) { if (trimmed.startsWith('# ')) { currentSection = trimmed.slice(2).toLowerCase() info[currentSection] = {} } continue } const colonIdx = trimmed.indexOf(':') if (colonIdx > 0) { const key = trimmed.slice(0, colonIdx) const value = trimmed.slice(colonIdx + 1) if (currentSection) { info[currentSection][key] = value } } } // Extract key metrics const memory = info.memory || {} const stats = info.stats || {} const clients = info.clients || {} const server = info.server || {} const keyspace = info.keyspace || {} // Parse keyspace hit/miss const hits = parseInt(stats.keyspace_hits) || 0 const misses = parseInt(stats.keyspace_misses) || 0 const hitRate = hits + misses > 0 ? ((hits / (hits + misses)) * 100).toFixed(1) : '0.0' const data = { timestamp: Date.now(), server: { version: server.redis_version, uptime: parseInt(server.uptime_in_seconds) || 0, mode: server.redis_mode || 'standalone', }, memory: { used: parseInt(memory.used_memory) || 0, usedHuman: memory.used_memory_human || '0B', rss: parseInt(memory.used_memory_rss) || 0, rssHuman: memory.used_memory_rss_human || '0B', peak: parseInt(memory.used_memory_peak) || 0, peakHuman: memory.used_memory_peak_human || '0B', fragRatio: parseFloat(memory.mem_fragmentation_ratio) || 0, }, stats: { opsPerSec: parseInt(stats.instantaneous_ops_per_sec) || 0, totalCommands: parseInt(stats.total_commands_processed) || 0, hits, misses, hitRate: parseFloat(hitRate), inputKbps: parseFloat(stats.instantaneous_input_kbps) || 0, outputKbps: parseFloat(stats.instantaneous_output_kbps) || 0, totalNetInput: parseInt(stats.total_net_input_bytes) || 0, totalNetOutput: parseInt(stats.total_net_output_bytes) || 0, expiredKeys: parseInt(stats.expired_keys) || 0, evictedKeys: parseInt(stats.evicted_keys) || 0, }, clients: { connected: parseInt(clients.connected_clients) || 0, blocked: parseInt(clients.blocked_clients) || 0, maxInput: parseInt(clients.client_recent_max_input_buffer) || 0, maxOutput: parseInt(clients.client_recent_max_output_buffer) || 0, }, keyspace, slowlog: slowlog.map(entry => ({ id: entry[0], timestamp: entry[1], duration: entry[2], command: Array.isArray(entry[3]) ? entry[3].join(' ') : String(entry[3]), })), } socket.emit(options.responseEvent, { status: 'ok', data, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/monitor/set.mjs000066400000000000000000000065351517716222300217070ustar00rootroot00000000000000export default async (options) => { const { socket, payload } = options; try { if (!socket.p3xrs.ioredis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } if (payload.enabled) { // Start MONITOR - need dedicated connections since MONITOR blocks if (!socket.p3xrs.ioredisMonitor) { const redis = socket.p3xrs.ioredis const isCluster = typeof redis.nodes === 'function' const monitors = [] const onMonitor = (time, args, source, database) => { socket.emit('monitor-data', { time: time, args: args, source: source, database: database, }) } if (isCluster) { // In cluster mode, monitor each master node const masterNodes = redis.nodes('master') for (const node of masterNodes) { for (let attempt = 0; attempt < 3; attempt++) { try { const monitor = await node.monitor() monitor.on('monitor', onMonitor) monitors.push(monitor) break } catch (e) { if (attempt === 2) { console.warn('MONITOR failed for cluster node, skipping:', e.message) } else { await new Promise(r => setTimeout(r, 200)) } } } } if (monitors.length === 0) { throw new Error('Failed to start MONITOR on any cluster node') } } else { // Standalone / sentinel let monitor for (let attempt = 0; attempt < 3; attempt++) { try { monitor = await redis.monitor() break } catch (e) { if (attempt === 2) throw e await new Promise(r => setTimeout(r, 200)) } } monitor.on('monitor', onMonitor) monitors.push(monitor) } socket.p3xrs.ioredisMonitor = monitors console.info('MONITOR started for', socket.id, isCluster ? `(${monitors.length} cluster nodes)` : '(standalone)') } } else { // Stop MONITOR if (socket.p3xrs.ioredisMonitor) { for (const monitor of socket.p3xrs.ioredisMonitor) { monitor.disconnect() } socket.p3xrs.ioredisMonitor = undefined console.info('MONITOR stopped for', socket.id) } } socket.emit(options.responseEvent, { status: 'ok' }) } catch (e) { console.error('Monitor error:', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/monitor/slowlog-reset.mjs000066400000000000000000000012011517716222300237030ustar00rootroot00000000000000import { ensureReadonlyConnection } from '../../shared.mjs' export default async (options) => { const { socket } = options try { ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis if (!redis) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } await redis.slowlog('RESET') socket.emit(options.responseEvent, { status: 'ok' }) } catch (e) { console.error('monitor/slowlog-reset failed', e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/probabilistic/000077500000000000000000000000001517716222300215275ustar00rootroot00000000000000src/service/socket.io/request/probabilistic/add.mjs000066400000000000000000000026251517716222300227770ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io probabilistic add' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { key, type, item, increment } = payload console.info(consolePrefix, type, key, item) let result switch (type) { case 'bloom': result = await redis.call('BF.ADD', key, item) break; case 'cuckoo': result = await redis.call('CF.ADD', key, item) break; case 'topk': result = await redis.call('TOPK.ADD', key, item) break; case 'cms': result = await redis.call('CMS.INCRBY', key, item, parseInt(increment) || 1) break; case 'tdigest': result = await redis.call('TDIGEST.ADD', key, parseFloat(item)) break; default: throw new Error('unsupported-probabilistic-type') } socket.emit(options.responseEvent, { status: 'ok', result: result, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/probabilistic/check.mjs000066400000000000000000000031471517716222300233240ustar00rootroot00000000000000const consolePrefix = 'socket.io probabilistic check' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const { key, type, item, quantile } = payload console.info(consolePrefix, type, key, item || quantile) let result switch (type) { case 'bloom': result = await redis.call('BF.EXISTS', key, item) break; case 'cuckoo': result = await redis.call('CF.EXISTS', key, item) break; case 'topk': { const raw = await redis.call('TOPK.LIST', key, 'WITHCOUNT') // Returns flat array [item, count, item, count, ...] const items = [] for (let i = 0; i < raw.length; i += 2) { items.push({ item: raw[i], count: parseInt(raw[i + 1]) || 0 }) } result = items break; } case 'cms': result = await redis.call('CMS.QUERY', key, item) break; case 'tdigest': result = await redis.call('TDIGEST.QUANTILE', key, parseFloat(quantile)) break; default: throw new Error('unsupported-probabilistic-type') } socket.emit(options.responseEvent, { status: 'ok', result: result, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/probabilistic/delete.mjs000066400000000000000000000017211517716222300235050ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io probabilistic delete' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { key, type, item } = payload console.info(consolePrefix, type, key, item) switch (type) { case 'cuckoo': await redis.call('CF.DEL', key, item) break; case 'tdigest': await redis.call('TDIGEST.RESET', key) break; default: throw new Error('unsupported-probabilistic-delete') } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/redis/000077500000000000000000000000001517716222300200075ustar00rootroot00000000000000src/service/socket.io/request/redis/console.mjs000066400000000000000000000051241517716222300221660ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const parser = sharedIoRedis.argumentParser const disabledCommands = ['subscribe', 'monitor', 'quit', 'psubscribe'] // Commands that have cluster-aware overrides on the Cluster class. // redis.call() bypasses these, so we invoke the instance method directly. const clusterOverriddenCommands = ['flushdb', 'flushall', 'dbsize'] const consolePrefix = 'socket.io console call' export default async (options) => { const {socket, payload} = options; const {command} = payload try { let redis = socket.p3xrs?.ioredis const commands = parser( command); let mainCommand = commands.shift() mainCommand = mainCommand.toLowerCase(); if (disabledCommands.includes(mainCommand)) { throw new Error('invalid_console_command') } // No live Redis client β€” console command cannot run. Throw a clean // message instead of crashing on `undefined.call`. if (!redis) { throw new Error('not_connected') } if (mainCommand !== 'select') { sharedIoRedis.ensureReadonlyConnection({ socket }) } console.info(consolePrefix, mainCommand, commands) let result if (clusterOverriddenCommands.includes(mainCommand) && typeof redis[mainCommand] === 'function') { result = await redis[mainCommand](...commands) } else { result = await redis.call(mainCommand, ...commands) } const defaultEmit = {} let generatedCommand = mainCommand if (commands.length > 0) { generatedCommand += ' ' + commands.join(' ') } switch (mainCommand) { case 'select': defaultEmit.database = parseInt(commands[0]) break; } /* switch (generatedCommand) { case 'client list': //result = result.split(' ') break; } */ //console.warn(consolePrefix, typeof result, result) /* try { const clone = JSON.parse(JSON.stringify(result)) console.warn(consolePrefix, typeof clone, clone) } catch(e) { console.warn(e) } */ socket.emit(options.responseEvent, Object.assign(defaultEmit, { status: 'ok', result: result, generatedCommand: generatedCommand, })) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/redis/refresh.mjs000066400000000000000000000012431517716222300221600ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' //const consolePrefix = 'socket.io refresh redis' export default async (options) => { const {socket, payload} = options; const redis = socket.p3xrs?.ioredis try { if (!redis) { throw new Error('not_connected') } await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/redis/save.mjs000066400000000000000000000010411517716222300214540ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket} = options; const redis = socket.p3xrs.ioredis try { sharedIoRedis.ensureReadonlyConnection({ socket }) await redis.bgsave() socket.emit(options.responseEvent, { status: 'ok', info: await redis.info(), }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/search/000077500000000000000000000000001517716222300201465ustar00rootroot00000000000000src/service/socket.io/request/search/hybrid.mjs000066400000000000000000000033301517716222300221410ustar00rootroot00000000000000/** * FT.HYBRID β€” hybrid search combining text + vector similarity (Redis 8.4+) * Requires a RediSearch index with a VECTOR field. */ export default async (options) => { const { socket, payload } = options try { const redis = socket.p3xrs.ioredis const { index, query, vectorField, vectorValues, count = 10, offset = 0, limit = 20 } = payload if (!index || !query || !vectorField || !vectorValues) { socket.emit(options.responseEvent, { status: 'error', error: 'Index, query, vectorField, and vectorValues are required', }) return } const vecBlob = Buffer.from(new Float32Array(vectorValues.map(Number)).buffer) const args = [ index, query, 'LIMIT', offset, limit, 'VECTOR', vectorField, count, vecBlob, ] const result = await redis.call('FT.HYBRID', ...args) // Parse like FT.SEARCH: [totalCount, key1, [field, value, ...], ...] const total = result[0] const docs = [] for (let i = 1; i < result.length; i += 2) { const key = result[i] const fields = result[i + 1] const doc = { _key: key } if (Array.isArray(fields)) { for (let j = 0; j < fields.length; j += 2) { doc[fields[j]] = fields[j + 1] } } docs.push(doc) } socket.emit(options.responseEvent, { status: 'ok', data: { total, docs }, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/search/index-create.mjs000066400000000000000000000023251517716222300232330ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket, payload} = options try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { name, prefix, schema } = payload if (!name || !schema || !Array.isArray(schema) || schema.length === 0) { socket.emit(options.responseEvent, { status: 'error', error: 'Index name and schema are required', }) return } // Build FT.CREATE command const args = [name, 'ON', 'HASH'] if (prefix) { args.push('PREFIX', '1', prefix) } args.push('SCHEMA') for (const field of schema) { args.push(field.name, field.type) if (field.sortable) args.push('SORTABLE') if (field.noindex) args.push('NOINDEX') } await redis.call('FT.CREATE', ...args) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/search/index-drop.mjs000066400000000000000000000010401517716222300227250ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' export default async (options) => { const {socket, payload} = options try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis await redis.call('FT.DROPINDEX', payload.index) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/search/index-info.mjs000066400000000000000000000013141517716222300227200ustar00rootroot00000000000000export default async (options) => { const {socket, payload} = options try { const redis = socket.p3xrs.ioredis const result = await redis.call('FT.INFO', payload.index) // Parse alternating key-value array into object const info = {} for (let i = 0; i < result.length; i += 2) { const key = result[i] const value = result[i + 1] info[key] = value } socket.emit(options.responseEvent, { status: 'ok', data: info, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/search/list.mjs000066400000000000000000000007361517716222300216420ustar00rootroot00000000000000export default async (options) => { const {socket} = options try { const redis = socket.p3xrs.ioredis const indexes = await redis.call('FT._LIST') socket.emit(options.responseEvent, { status: 'ok', data: Array.isArray(indexes) ? indexes : [], }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/search/query.mjs000066400000000000000000000025121517716222300220260ustar00rootroot00000000000000export default async (options) => { const {socket, payload} = options try { const redis = socket.p3xrs.ioredis const { index, query, offset, limit } = payload if (!index || !query) { socket.emit(options.responseEvent, { status: 'error', error: 'Index and query are required', }) return } const args = [index, query, 'LIMIT', offset || 0, limit || 20] const result = await redis.call('FT.SEARCH', ...args) // Parse FT.SEARCH result: [totalCount, key1, [field, value, ...], key2, [...], ...] const total = result[0] const docs = [] for (let i = 1; i < result.length; i += 2) { const key = result[i] const fields = result[i + 1] const doc = { _key: key } if (Array.isArray(fields)) { for (let j = 0; j < fields.length; j += 2) { doc[fields[j]] = fields[j + 1] } } docs.push(doc) } socket.emit(options.responseEvent, { status: 'ok', data: { total, docs }, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/settings/000077500000000000000000000000001517716222300205415ustar00rootroot00000000000000src/service/socket.io/request/settings/language.mjs000066400000000000000000000007551517716222300230460ustar00rootroot00000000000000export default async (options) => { const { socket, payload } = options; try { if (global.p3xre) { global.p3xre.setLanguage({ key: payload.key }) } socket.emit(options.responseEvent, { status: 'ok', key: payload.key, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/settings/subscription.mjs000066400000000000000000000041661517716222300240070ustar00rootroot00000000000000export default async (options) => { const { socket, payload } = options; try { if (!socket.p3xrs.ioredisSubscriber) { socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' }) return } //sharedIoRedis.ensureReadonlyConnection({ socket }) //console.log('Unsubscribing from all patterns'); await socket.p3xrs.ioredisSubscriber.punsubscribe(); //console.log('All patterns unsubscribed'); socket.p3xrs.ioredisSubscriber.removeAllListeners('pmessage'); socket.p3xrs.ioredisSubscriber.removeAllListeners('pmessageBuffer'); //console.log('Removed all pmessage listeners'); // Updating subscription settings socket.p3xrs.subscription = payload.subscription; if (typeof payload.subscriberPattern !== 'string' || payload.subscriberPattern.trim().length === 0) { payload.subscriberPattern = '*'; // Default pattern } if (socket.p3xrs.subscription === true) { // Subscribe to the pattern //console.log('socket.p3xrs.ioredisSubscriber.psubscribe', payload.subscriberPattern) await socket.p3xrs.ioredisSubscriber.psubscribe(payload.subscriberPattern); // Use pmessageBuffer to preserve raw binary data (e.g. msgpack from socket.io-adapter) // Socket.IO will transmit the Buffer as binary, frontend handles decoding socket.p3xrs.ioredisSubscriber.on('pmessageBuffer', (pattern, channel, message) => { const channelStr = channel.toString('utf-8'); console.log('socket.p3xrs.ioredisSubscriber.on(pmessageBuffer)', pattern.toString('utf-8'), channelStr) socket.emit('pubsub-message', { channel: channelStr, message: message, }); }); } // Confirm successful setup socket.emit(options.responseEvent, { status: 'ok' }); } catch (e) { console.error('Subscription error:', e); socket.emit(options.responseEvent, { status: 'error', error: e.message }); } }; src/service/socket.io/request/timeseries/000077500000000000000000000000001517716222300210525ustar00rootroot00000000000000src/service/socket.io/request/timeseries/add.mjs000066400000000000000000000015171517716222300223210ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io timeseries add' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const key = payload.key const timestamp = payload.timestamp || '*' const value = payload.value console.info(consolePrefix, key, timestamp, value) const result = await redis.call('TS.ADD', key, timestamp, value, 'ON_DUPLICATE', 'LAST') socket.emit(options.responseEvent, { status: 'ok', timestamp: result, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/timeseries/alter.mjs000066400000000000000000000022141517716222300226730ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io timeseries alter' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const key = payload.key const args = [key] if (payload.retention !== undefined) { args.push('RETENTION', parseInt(payload.retention)) } if (payload.duplicatePolicy) { args.push('DUPLICATE_POLICY', payload.duplicatePolicy) } if (payload.labels !== undefined) { args.push('LABELS') if (payload.labels && payload.labels.trim().length > 0) { args.push(...payload.labels.trim().split(/\s+/)) } } console.info(consolePrefix, args) await redis.call('TS.ALTER', ...args) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/timeseries/del.mjs000066400000000000000000000014201517716222300223260ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io timeseries del' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const key = payload.key const from = payload.from const to = payload.to console.info(consolePrefix, key, from, to) const deleted = await redis.call('TS.DEL', key, from, to) socket.emit(options.responseEvent, { status: 'ok', deleted: deleted, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/timeseries/info.mjs000066400000000000000000000033521517716222300225230ustar00rootroot00000000000000const consolePrefix = 'socket.io timeseries info' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const key = payload.key console.info(consolePrefix, key) const raw = await redis.call('TS.INFO', key) // TS.INFO returns flat array: [field, value, field, value, ...] const info = {} for (let i = 0; i < raw.length; i += 2) { const field = raw[i] let value = raw[i + 1] // Parse nested label pairs [[key, value], ...] if (field === 'labels' && Array.isArray(value)) { const labels = {} for (const pair of value) { if (Array.isArray(pair) && pair.length === 2) { labels[pair[0]] = pair[1] } } value = labels } // Parse rules [[destKey, bucketDuration, aggregationType], ...] if (field === 'rules' && Array.isArray(value)) { value = value.map(rule => { if (Array.isArray(rule)) { return { destKey: rule[0], bucketDuration: rule[1], aggregationType: rule[2], } } return rule }) } info[field] = value } socket.emit(options.responseEvent, { status: 'ok', data: info, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/timeseries/mrange.mjs000066400000000000000000000036021517716222300230370ustar00rootroot00000000000000const consolePrefix = 'socket.io timeseries mrange' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const from = payload.from || '-' const to = payload.to || '+' const filter = payload.filter if (!filter) { socket.emit(options.responseEvent, { status: 'ok', data: [], }) return } const args = [from, to, 'FILTER', filter] if (payload.aggregation && payload.aggregation.type && payload.aggregation.timeBucket) { args.push('AGGREGATION', payload.aggregation.type, payload.aggregation.timeBucket) } if (payload.count) { args.push('COUNT', payload.count) } console.info(consolePrefix, args) const raw = await redis.call('TS.MRANGE', ...args) // raw is [[key, labels, [[ts, val], ...]], ...] const data = raw.map(entry => { const key = entry[0] const labels = {} if (Array.isArray(entry[1])) { for (const pair of entry[1]) { if (Array.isArray(pair) && pair.length === 2) { labels[pair[0]] = pair[1] } } } const points = (entry[2] || []).map(point => ({ timestamp: typeof point[0] === 'number' ? point[0] : parseInt(point[0]), value: typeof point[1] === 'number' ? point[1] : parseFloat(point[1]), })) return { key, labels, data: points } }) socket.emit(options.responseEvent, { status: 'ok', data: data, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/timeseries/range.mjs000066400000000000000000000026001517716222300226570ustar00rootroot00000000000000const consolePrefix = 'socket.io timeseries range' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const key = payload.key const from = payload.from || '-' const to = payload.to || '+' const args = [key, from, to] // Optional aggregation: { type: 'avg'|'min'|'max'|'sum'|'count'|..., timeBucket: 5000 } if (payload.aggregation && payload.aggregation.type && payload.aggregation.timeBucket) { args.push('AGGREGATION', payload.aggregation.type, payload.aggregation.timeBucket) } // Optional count limit if (payload.count) { args.push('COUNT', payload.count) } console.info(consolePrefix, args) const raw = await redis.call('TS.RANGE', ...args) // raw is [[timestamp, value], [timestamp, value], ...] const data = raw.map(entry => ({ timestamp: typeof entry[0] === 'number' ? entry[0] : parseInt(entry[0]), value: typeof entry[1] === 'number' ? entry[1] : parseFloat(entry[1]), })) socket.emit(options.responseEvent, { status: 'ok', data: data, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/vectorset/000077500000000000000000000000001517716222300207175ustar00rootroot00000000000000src/service/socket.io/request/vectorset/add.mjs000066400000000000000000000017431517716222300221670ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io vectorset add' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { key, element, values, attributes } = payload const dim = values.length const args = [key, 'VALUES', dim, ...values.map(Number), element] // Optional attributes: "key\nvalue\nkey\nvalue" if (attributes && typeof attributes === 'string' && attributes.trim()) { args.push('SETATTR', attributes.trim()) } const result = await redis.call('VADD', ...args) socket.emit(options.responseEvent, { status: 'ok', result: result, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/vectorset/elements.mjs000066400000000000000000000021531517716222300232470ustar00rootroot00000000000000const consolePrefix = 'socket.io vectorset elements' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const { key } = payload const dim = parseInt(await redis.call('VDIM', key)) || 3 const count = parseInt(await redis.call('VCARD', key)) || 100 // Use VSIM with zero vector to list all elements const zeroVec = new Array(dim).fill(0) const raw = await redis.call('VSIM', key, 'VALUES', dim, ...zeroVec, 'COUNT', count, 'WITHSCORES') // Parse flat array [element, score, element, score, ...] const elements = [] for (let i = 0; i < raw.length; i += 2) { elements.push({ element: raw[i], score: parseFloat(raw[i + 1]) || 0 }) } socket.emit(options.responseEvent, { status: 'ok', elements: elements, dim: dim, count: count, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/vectorset/getattr.mjs000066400000000000000000000015761517716222300231150ustar00rootroot00000000000000const consolePrefix = 'socket.io vectorset getattr' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const { key, element } = payload const raw = await redis.call('VGETATTR', key, element) // Parse flat string "field\nvalue\nfield\nvalue" or null const attrs = {} if (raw && typeof raw === 'string') { const parts = raw.split('\n') for (let i = 0; i < parts.length; i += 2) { if (parts[i]) attrs[parts[i]] = parts[i + 1] || '' } } socket.emit(options.responseEvent, { status: 'ok', attributes: attrs, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/vectorset/remove.mjs000066400000000000000000000011661517716222300227330ustar00rootroot00000000000000import * as sharedIoRedis from '../../shared.mjs' const consolePrefix = 'socket.io vectorset remove' export default async (options) => { const {socket, payload} = options; try { sharedIoRedis.ensureReadonlyConnection({ socket }) const redis = socket.p3xrs.ioredis const { key, element } = payload await redis.call('VREM', key, element) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/vectorset/sim.mjs000066400000000000000000000023561517716222300222300ustar00rootroot00000000000000const consolePrefix = 'socket.io vectorset sim' export default async (options) => { const {socket, payload} = options; try { const redis = socket.p3xrs.ioredis const { key, mode, element, values, count, filter } = payload const n = parseInt(count) || 10 let raw if (mode === 'element') { const args = ['VSIM', key, 'ELE', element, 'COUNT', n, 'WITHSCORES'] if (filter) args.push('FILTER', filter) raw = await redis.call(...args) } else { // mode === 'vector' const dim = values.length const args = ['VSIM', key, 'VALUES', dim, ...values.map(Number), 'COUNT', n, 'WITHSCORES'] if (filter) args.push('FILTER', filter) raw = await redis.call(...args) } const results = [] for (let i = 0; i < raw.length; i += 2) { results.push({ element: raw[i], score: parseFloat(raw[i + 1]) || 0 }) } socket.emit(options.responseEvent, { status: 'ok', results: results, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/shared.mjs000066400000000000000000000430371517716222300172010ustar00rootroot00000000000000import { cloneDeep } from 'lodash-es' const triggerDisconnect = (options) => { const {connectionId, code, socket} = options if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { delete p3xrs.redisConnections[connectionId] socket.p3xrs.io.emit('redis-disconnected', { connectionId: connectionId, status: 'code', code: code }) sendStatus({ socket: socket }) } } const sendStatus = (options) => { const {socket} = options const redisConnections = {} Object.keys(p3xrs.redisConnections).forEach((redisConnectionKey) => { redisConnections[redisConnectionKey] = {} Object.keys(p3xrs.redisConnections[redisConnectionKey]).forEach(redisConnectionKey2 => { redisConnections[redisConnectionKey][redisConnectionKey2] = p3xrs.redisConnections[redisConnectionKey][redisConnectionKey2] }) }) socket.p3xrs.io.emit('redis-status', { redisConnections: redisConnections, }) } const consolePrefixDisconnectRedis = 'socket.io shared disconnect redis' const disconnectRedis = (options) => { const {socket} = options //console.warn(consolePrefixDisconnectRedis, `${socket.p3xrs.connectionId} !== ${connection.id}`) if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId)) { console.warn(consolePrefixDisconnectRedis, `includes ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id)} length === 1 ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length}`) if (p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id) && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 1) { //console.warn(consolePrefixDisconnectRedis, p3xrs.redisConnections[socket.p3xrs.connectionId]) //p3xrs.redisConnections[socket.p3xrs.connectionId].ioredis.disconnect() delete p3xrs.redisConnections[socket.p3xrs.connectionId] } else { let connectionIndexExisting = p3xrs.redisConnections[socket.p3xrs.connectionId].clients.indexOf(socket.id); console.warn(consolePrefixDisconnectRedis, socket.p3xrs.connectionId, p3xrs.redisConnections[socket.p3xrs.connectionId].clients, socket.id, connectionIndexExisting) if (connectionIndexExisting > -1) { p3xrs.redisConnections[socket.p3xrs.connectionId].clients.splice(connectionIndexExisting, 1) } } } if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId) && p3xrs.redisConnections[socket.p3xrs.connectionId].hasOwnProperty('clients') && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 0) { delete p3xrs.redisConnections[socket.p3xrs.connectionId] } disconnectRedisIo(options) socket.p3xrs.connectionId = undefined } const sendConnections = (options) => { const {socket} = options const connections = cloneDeep(p3xrs.connections); let connectionsList = connections.list.map(connection => { delete connection.password delete connection.tlsCrt delete connection.tlsKey delete connection.tlsCa delete connection.sshPassword delete connection.sshPrivateKey //TODO fix secured nodes password if (Array.isArray(connection.nodes)) { connection.nodes = connection.nodes.map(node => { delete node.password return node }) } return connection }) connections.list = connectionsList socket.p3xrs.io.emit('connections', { status: 'ok', connections: connections }) } const disconnectRedisIo = (options) => { const {socket} = options console.warn('shared disconnectRedisIo', 'try') if (socket.p3xrs.ioredis !== undefined) { console.warn('shared disconnectRedisIo', 'executed') socket.p3xrs.ioredis.disconnect() socket.p3xrs.ioredisSubscriber.disconnect() socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined } if (Array.isArray(socket.p3xrs.tunnels) && socket.p3xrs.tunnels.length > 0) { for (const server of socket.p3xrs.tunnels) { server.close() } socket.p3xrs.tunnels = [] } if (socket.p3xrs.sshClient) { socket.p3xrs.sshClient.end() socket.p3xrs.sshClient = undefined } } const getStreamKeys = (options) => { const {redis, } = options let {dbsize, maxKeys} = options return new Promise(async (resolve, reject) => { try { /* if (dbsize === undefined) { dbsize = await redis.dbsize() } */ if (isNaN(maxKeys) || maxKeys < 5 || maxKeys > 100000) { maxKeys = 10000 } //console.warn('check if received max keys', maxKeys, typeof maxKeys, !isNaN(maxKeys), maxKeys < 5, maxKeys > 100000) /* let count = 100 if (dbsize > 110000) { count = 10000 } else if (dbsize > 11000) { count = 1000 } */ let count = Math.round(maxKeys / 10) if (count < 5) { count = 5 } //console.warn('socket.io getStreamKeys dbsize', dbsize, 'count', count, 'maxKeys', maxKeys) const stream = redis.scanStream({ match: options.match, count: count }); let keys = []; let ended = false stream.on('data', (resultKeys) => { /* keys = keys.concat(resultKeys); if (maxKeys && keys.length >= maxKeys && !ended) { ended = true console.warn('reached max key count', maxKeys, 'found', keys.length, 'keys our of unknown total') //stream.pause() //stream.destroy() stream.emit('end') } */ if (maxKeys && keys.length < maxKeys) { keys = keys.concat(resultKeys); if (keys.length >= maxKeys) { ended = true resolve(keys) //stream.emit('end') } } else if (!ended) { ended = true resolve(keys) } }); stream.on('end', () => { if (ended) { return } resolve(keys); }); /* stream.on('error', (error) => { console.error('getStreamKeys stream', error) reject(error) }) */ } catch (e) { reject(e) } }) } /* const getStreamTypedKeys = (options) => { const { redis, key, match } = options let { scan } = options if (scan === undefined) { scan = 'scanStream' } return new Promise((resolve, reject) => { let stream; if (scan === 'scanStream') { stream = redis[scan]({ match: match }); } else { stream = redis[scan](key, { match: match }); } let keys = []; stream.on('data', (resultKeys) => { keys = keys.concat(resultKeys); }); stream.on('end', async () => { try { resolve(keys); } catch (e) { console.error(e); reject(e) } }); }) } */ const getKeysInfo = async (options) => { const {redis, keys} = options; const keyTypePipeline = redis.pipeline() for (let key of keys) { keyTypePipeline.type(key) keyTypePipeline.ttl(key) } const keyTypeAndTtlResults = await keyTypePipeline.exec(); const result = {} const complexLengthPipeline = redis.pipeline() for (let keysIndex in keys) { const pipelineIndex = keysIndex * 2 const keyType = keyTypeAndTtlResults[pipelineIndex] const keyTtl = keyTypeAndTtlResults[pipelineIndex + 1] const key = keys[keysIndex] const obj = { type: keyType[1], ttl: keyTtl[1], } // Normalize ReJSON-RL to json for the client if (obj.type === 'ReJSON-RL') { obj.type = 'json' } // Normalize TSDB-TYPE to timeseries for the client if (obj.type === 'TSDB-TYPE') { obj.type = 'timeseries' } // Normalize RedisBloom module types if (obj.type === 'MBbloom--') obj.type = 'bloom' if (obj.type === 'MBbloomCF') obj.type = 'cuckoo' if (obj.type === 'TopK-TYPE') obj.type = 'topk' if (obj.type === 'CMSk-TYPE') obj.type = 'cms' if (obj.type === 'TDIS-TYPE') obj.type = 'tdigest' switch (obj.type) { case 'stream': complexLengthPipeline.xlen(key) break; case 'hash': complexLengthPipeline.hlen(key) break; case 'list': complexLengthPipeline.llen(key) break; case 'set': complexLengthPipeline.scard(key) break; case 'zset': complexLengthPipeline.zcard(key) break; case 'timeseries': complexLengthPipeline.call('TS.INFO', key) break; case 'bloom': complexLengthPipeline.call('BF.INFO', key) break; case 'cuckoo': complexLengthPipeline.call('CF.INFO', key) break; case 'topk': complexLengthPipeline.call('TOPK.INFO', key) break; case 'cms': complexLengthPipeline.call('CMS.INFO', key) break; case 'tdigest': complexLengthPipeline.call('TDIGEST.INFO', key) break; case 'vectorset': complexLengthPipeline.call('VCARD', key) break; } result[key] = obj } const lengthsPipeline = await complexLengthPipeline.exec() for (let keysIndex in keys) { const key = keys[keysIndex] const obj = result[key] // Only consume a pipeline result for types that added a command above const typesWithPipeline = ['stream', 'hash', 'list', 'set', 'zset', 'timeseries', 'bloom', 'cuckoo', 'topk', 'cms', 'tdigest', 'vectorset'] if (!typesWithPipeline.includes(obj.type)) { continue } const lengthPipelineElement = lengthsPipeline.shift() if (lengthPipelineElement === undefined) { continue } if (obj.type === 'timeseries') { // TS.INFO returns flat array [field, value, ...]; extract totalSamples const tsInfo = lengthPipelineElement[1] if (Array.isArray(tsInfo)) { for (let i = 0; i < tsInfo.length; i += 2) { if (tsInfo[i] === 'totalSamples') { obj.length = tsInfo[i + 1] break } } } } else if (obj.type === 'bloom' || obj.type === 'cuckoo') { // BF.INFO / CF.INFO returns flat array; extract "Number of items inserted" const info = lengthPipelineElement[1] if (Array.isArray(info)) { for (let i = 0; i < info.length; i += 2) { if (info[i] === 'Number of items inserted') { obj.length = info[i + 1] break } } } } else if (obj.type === 'topk') { // TOPK.INFO returns flat array; extract "k" const info = lengthPipelineElement[1] if (Array.isArray(info)) { for (let i = 0; i < info.length; i += 2) { if (info[i] === 'k') { obj.length = info[i + 1] break } } } } else if (obj.type === 'cms') { // CMS.INFO returns flat array; extract "count" const info = lengthPipelineElement[1] if (Array.isArray(info)) { for (let i = 0; i < info.length; i += 2) { if (info[i] === 'count') { obj.length = info[i + 1] break } } } } else if (obj.type === 'tdigest') { // TDIGEST.INFO returns flat array; extract "Merged nodes" const info = lengthPipelineElement[1] if (Array.isArray(info)) { for (let i = 0; i < info.length; i += 2) { if (info[i] === 'Merged nodes') { obj.length = info[i + 1] break } } } } else if (obj.type === 'vectorset') { // VCARD returns integer directly obj.length = lengthPipelineElement[1] } else { obj.length = lengthPipelineElement[1] } } return result; } const ensureReadonlyConnections = () => { if (p3xrs.cfg.readonlyConnections === true) { const errorCode = new Error('readonly-connection-mode') throw errorCode; } } const ensureReadonlyConnection = ({ socket }) => { if (socket.p3xrs.readonly === true) { const errorCode = new Error('readonly-connection-mode') throw errorCode; } } const getFullInfo = async (options) => { const {redis} = options; let {payload} = options if (payload === undefined) { payload = {} } const dbsize = await redis.dbsize() const results = await Promise.all([ redis.info(), getStreamKeys({ dbsize: dbsize, redis: redis, match: payload.match, maxKeys: payload.maxKeys, }), redis.pubsub('channels', '*'), // redis.infoObject(), ]) const keys = results[1] let keysInfo = {} if (keys.length < 110000) { keysInfo = await getKeysInfo({ redis: redis, keys: keys, }) } // const keysInfo = [] const result = { info: results[0], // infoObject: results[3], keys: keys, keysInfo: keysInfo, dbsize: dbsize, channels: results[2] } //console.log('get full info', result) return result } const getFullInfoAndSendSocket = async (options) => { const {redis, socket, payload, setDb} = options if (setDb === true) { try { await redis.call('select', payload.db || 0) } catch(e) { console.warn(e) } } const result = await getFullInfo({ redis: redis, payload: payload, }) let {extend} = options if (extend === undefined) { extend = {} } socket.emit(options.responseEvent, Object.assign(extend, { status: 'ok', info: result.info, // infoObject: result.infoObject, keys: result.keys, keysInfo: result.keysInfo, keysInfoFetchedAt: Date.now(), dbsize: result.dbsize, })) } const argumentParser = (input, sep, keepQuotes) => { const separator = sep || /\s/g; let singleQuoteOpen = false; let doubleQuoteOpen = false; let tokenBuffer = []; const ret = []; console.log('argumentParser input', input) const arr = input.split(''); for (let i = 0; i < arr.length; ++i) { let element = arr[i]; let matches = element.match(separator); if (element === "'" && !doubleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } singleQuoteOpen = !singleQuoteOpen; continue; } else if (element === '"' && !singleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } doubleQuoteOpen = !doubleQuoteOpen; continue; } if (!singleQuoteOpen && !doubleQuoteOpen && matches) { if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); tokenBuffer = []; } else if (!!sep) { ret.push(element); } } else { tokenBuffer.push(element); } } if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); } else if (!!sep) { ret.push(''); } return ret; } const parseModuleList = (rawList) => { if (!Array.isArray(rawList)) return [] const modules = [] for (const entry of rawList) { if (!Array.isArray(entry)) continue const mod = {} for (let i = 0; i < entry.length; i += 2) { mod[entry[i]] = entry[i + 1] } if (mod.name) modules.push(mod) } return modules } const detectModules = async (redis) => { try { const rawList = await redis.call('MODULE', 'LIST') return parseModuleList(rawList) } catch (e) { // MODULE LIST not supported or disabled return [] } } export { argumentParser, ensureReadonlyConnections, triggerDisconnect, getStreamKeys, disconnectRedisIo, sendConnections, sendStatus, disconnectRedis, getKeysInfo, getFullInfo, getFullInfoAndSendSocket, ensureReadonlyConnection, parseModuleList, detectModules, } src/service/socket.io/socket.mjs000066400000000000000000000116271517716222300172230ustar00rootroot00000000000000import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' import * as socketIoShared from './shared.mjs' import { isSnapshot, version } from '../../lib/resolve-version.mjs' // Auto-discover request handlers from request/$area/$function.mjs const __dirname = path.dirname(fileURLToPath(import.meta.url)) const requestDir = path.join(__dirname, 'request') const validActions = new Set() for (const entry of fs.readdirSync(requestDir, { withFileTypes: true })) { if (!entry.isDirectory()) continue for (const file of fs.readdirSync(path.join(requestDir, entry.name), { withFileTypes: true })) { if (!file.isFile() || !file.name.endsWith('.mjs')) continue validActions.add(`${entry.name}/${file.name.slice(0, -4)}`) } } console.info(`socket.io discovered ${validActions.size} request handlers:`, [...validActions].sort().join(', ')) export default (io) => { io.on('connect', function (socket) { //const token = socket.handshake.query.token; socket.p3xrs = { address: socket.handshake.headers.origin, connectedAt: new Date(), connectionId: undefined, io: io, ioredis: undefined, ioredisSubscriber: undefined, tunnels: [], sshClient: undefined, readonly: undefined, // commands: undefined, subsciber: false, } console.info(`socket.io connected ${socket.id}`); socket.on('disconnect', function () { console.warn('socket.p3xrs.connectionId', socket.p3xrs.connectionId) if (socket.p3xrs.connectionId !== undefined) { const connectionId = socket.p3xrs.connectionId; if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { const redisConnectionIndex = p3xrs.redisConnections[connectionId].clients.indexOf(socket.id); if (redisConnectionIndex !== -1) { p3xrs.redisConnections[connectionId].clients.splice(redisConnectionIndex, 1); } if (p3xrs.redisConnections[connectionId].clients.length === 0) { delete p3xrs.redisConnections[connectionId] } socketIoShared.disconnectRedisIo({ socket: socket, }) } } // Stop MONITOR if active if (socket.p3xrs.ioredisMonitor) { for (const monitor of socket.p3xrs.ioredisMonitor) { try { monitor.disconnect() } catch {} } socket.p3xrs.ioredisMonitor = undefined } // Call on disconnect. console.info('socket.io disconnected %s', socket.id); socketIoShared.sendStatus({ socket: socket, }) socketIoShared.disconnectRedis({ socket: socket, }) }); socket.on('p3xr-request', (options) => { options.socket = socket; options.responseEvent = `p3xr-response-${options.requestId}` if (options?.action && typeof options.action === 'string' && validActions.has(options.action)) { import(`./request/${options.action}.mjs`).then(mod => mod.default(options)).catch(err => { console.error('failed to load request handler', options.action, err) socket.emit(options.responseEvent, { status: 'error', error: err.message, }) }) } else { console.warn('trying bad action socket.on p3xr-request with options', options) } }) let dividers = [ ":", "/", "|", "-", "@" ] if (p3xrs.cfg.hasOwnProperty('treeDividers') && Array.isArray(p3xrs.cfg.treeDividers)) { dividers = p3xrs.cfg.treeDividers } socket.emit('configuration', { readonlyConnections: p3xrs.cfg.readonlyConnections === true, snapshot: isSnapshot, treeDividers: dividers, version: version, hasGroqApiKey: !!(p3xrs.cfg.groqApiKey && p3xrs.cfg.groqApiKey.startsWith('gsk_') && p3xrs.cfg.groqApiKey.length > 20), groqApiKeyMasked: p3xrs.cfg.groqApiKey && p3xrs.cfg.groqApiKey.length > 8 ? `${p3xrs.cfg.groqApiKey.slice(0, 4)}...${p3xrs.cfg.groqApiKey.slice(-4)}` : '', groqApiKeyReadonly: p3xrs.cfg.groqApiKeyReadonly === true, aiEnabled: p3xrs.cfg.aiEnabled !== false, aiUseOwnKey: p3xrs.cfg.aiUseOwnKey === true, groqMaxTokens: p3xrs.cfg.groqMaxTokens || 16384, }) socketIoShared.sendStatus({ socket: socket, }) socketIoShared.sendConnections({ socket: socket, }) }); } yarn.lock000066400000000000000000006052631517716222300127230ustar00rootroot00000000000000# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 "@gar/promise-retry@^1.0.0", "@gar/promise-retry@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz#65e726428e794bc4453948e0a41e6de4215ce8b0" integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA== "@ioredis/commands@1.5.1": version "1.5.1" resolved "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz" integrity sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw== "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" string-width-cjs "npm:string-width@^4.2.0" strip-ansi "^7.0.1" strip-ansi-cjs "npm:strip-ansi@^6.0.1" wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" "@isaacs/fs-minipass@^4.0.0": version "4.0.1" resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz" integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== dependencies: minipass "^7.0.4" "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz" integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/source-map@^0.3.3": version "0.3.11" resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz" integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== dependencies: "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.5" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.31" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" "@msgpack/msgpack@^3.1.3": version "3.1.3" resolved "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz" integrity sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA== "@npmcli/agent@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz" integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q== dependencies: agent-base "^7.1.0" http-proxy-agent "^7.0.0" https-proxy-agent "^7.0.1" lru-cache "^10.0.1" socks-proxy-agent "^8.0.3" "@npmcli/agent@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-4.0.0.tgz#2bb2b1c0a170940511554a7986ae2a8be9fedcce" integrity sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA== dependencies: agent-base "^7.1.0" http-proxy-agent "^7.0.0" https-proxy-agent "^7.0.1" lru-cache "^11.2.1" socks-proxy-agent "^8.0.3" "@npmcli/arborist@^9.4.3": version "9.4.3" resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-9.4.3.tgz#50be500c61927a73c8df364b4dde057627b3b9c0" integrity sha512-YhkR7XFdO7OBr8U1qs7DA7PmhSJXg59rLqd53jmeJ4pYe8WTCAsUZsKqxX7KKPEgAO5K7D/SjbyPUrBes9aP6Q== dependencies: "@gar/promise-retry" "^1.0.0" "@isaacs/string-locale-compare" "^1.1.0" "@npmcli/fs" "^5.0.0" "@npmcli/installed-package-contents" "^4.0.0" "@npmcli/map-workspaces" "^5.0.0" "@npmcli/metavuln-calculator" "^9.0.2" "@npmcli/name-from-folder" "^4.0.0" "@npmcli/node-gyp" "^5.0.0" "@npmcli/package-json" "^7.0.0" "@npmcli/query" "^5.0.0" "@npmcli/redact" "^4.0.0" "@npmcli/run-script" "^10.0.0" bin-links "^6.0.0" cacache "^20.0.1" common-ancestor-path "^2.0.0" hosted-git-info "^9.0.0" json-stringify-nice "^1.1.4" lru-cache "^11.2.1" minimatch "^10.0.3" nopt "^9.0.0" npm-install-checks "^8.0.0" npm-package-arg "^13.0.0" npm-pick-manifest "^11.0.1" npm-registry-fetch "^19.0.0" pacote "^21.0.2" parse-conflict-json "^5.0.1" proc-log "^6.0.0" proggy "^4.0.0" promise-all-reject-late "^1.0.0" promise-call-limit "^3.0.1" semver "^7.3.7" ssri "^13.0.0" treeverse "^3.0.0" walk-up-path "^4.0.0" "@npmcli/config@^10.8.1": version "10.8.1" resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-10.8.1.tgz#36dd459a03cda0fa9211df9f669bd1b2ac46497b" integrity sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ== dependencies: "@npmcli/map-workspaces" "^5.0.0" "@npmcli/package-json" "^7.0.0" ci-info "^4.0.0" ini "^6.0.0" nopt "^9.0.0" proc-log "^6.0.0" semver "^7.3.5" walk-up-path "^4.0.0" "@npmcli/fs@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz" integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q== dependencies: semver "^7.3.5" "@npmcli/fs@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-5.0.0.tgz#674619771907342b3d1ac197aaf1deeb657e3539" integrity sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og== dependencies: semver "^7.3.5" "@npmcli/git@^6.0.0": version "6.0.1" resolved "https://registry.npmjs.org/@npmcli/git/-/git-6.0.1.tgz" integrity sha512-BBWMMxeQzalmKadyimwb2/VVQyJB01PH0HhVSNLHNBDZN/M/h/02P6f8fxedIiFhpMj11SO9Ep5tKTBE7zL2nw== dependencies: "@npmcli/promise-spawn" "^8.0.0" ini "^5.0.0" lru-cache "^10.0.1" npm-pick-manifest "^10.0.0" proc-log "^5.0.0" promise-inflight "^1.0.1" promise-retry "^2.0.1" semver "^7.3.5" which "^5.0.0" "@npmcli/git@^7.0.0": version "7.0.2" resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-7.0.2.tgz#680c3271fe51401c07ee41076be678851e600ff0" integrity sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg== dependencies: "@gar/promise-retry" "^1.0.0" "@npmcli/promise-spawn" "^9.0.0" ini "^6.0.0" lru-cache "^11.2.1" npm-pick-manifest "^11.0.1" proc-log "^6.0.0" semver "^7.3.5" which "^6.0.0" "@npmcli/installed-package-contents@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz" integrity sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q== dependencies: npm-bundled "^4.0.0" npm-normalize-package-bin "^4.0.0" "@npmcli/installed-package-contents@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz#18e5070704cfe0278f9ae48038558b6efd438426" integrity sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA== dependencies: npm-bundled "^5.0.0" npm-normalize-package-bin "^5.0.0" "@npmcli/map-workspaces@^5.0.0", "@npmcli/map-workspaces@^5.0.3": version "5.0.3" resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz#5b887ec0b535a2ba64d1d338867326a2b9c041d1" integrity sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw== dependencies: "@npmcli/name-from-folder" "^4.0.0" "@npmcli/package-json" "^7.0.0" glob "^13.0.0" minimatch "^10.0.3" "@npmcli/metavuln-calculator@^9.0.2", "@npmcli/metavuln-calculator@^9.0.3": version "9.0.3" resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz#57b330f3fb8ca34db2782ad5349ea4384bed9c96" integrity sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg== dependencies: cacache "^20.0.0" json-parse-even-better-errors "^5.0.0" pacote "^21.0.0" proc-log "^6.0.0" semver "^7.3.5" "@npmcli/name-from-folder@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz#b4d516ae4fab5ed4e8e8032abff3488703fc24a3" integrity sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg== "@npmcli/node-gyp@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz" integrity sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA== "@npmcli/node-gyp@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz#35475a58b5d791764a7252231197a14deefe8e47" integrity sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ== "@npmcli/package-json@^6.0.0": version "6.1.1" resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.1.1.tgz" integrity sha512-d5qimadRAUCO4A/Txw71VM7UrRZzV+NPclxz/dc+M6B2oYwjWTjqh8HA/sGQgs9VZuJ6I/P7XIAlJvgrl27ZOw== dependencies: "@npmcli/git" "^6.0.0" glob "^10.2.2" hosted-git-info "^8.0.0" json-parse-even-better-errors "^4.0.0" proc-log "^5.0.0" semver "^7.5.3" validate-npm-package-license "^3.0.4" "@npmcli/package-json@^7.0.0", "@npmcli/package-json@^7.0.5": version "7.0.5" resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-7.0.5.tgz#e29481dfc586d1625a6553799e6bec52ae0487a5" integrity sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ== dependencies: "@npmcli/git" "^7.0.0" glob "^13.0.0" hosted-git-info "^9.0.0" json-parse-even-better-errors "^5.0.0" proc-log "^6.0.0" semver "^7.5.3" spdx-expression-parse "^4.0.0" "@npmcli/promise-spawn@^8.0.0": version "8.0.2" resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz" integrity sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ== dependencies: which "^5.0.0" "@npmcli/promise-spawn@^9.0.0", "@npmcli/promise-spawn@^9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz#20e80cbdd2f24ad263a15de3ebbb1673cb82005b" integrity sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q== dependencies: which "^6.0.0" "@npmcli/query@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-5.0.0.tgz#c8cb9ec42c2ef149077282e948dc068ecc79ee11" integrity sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ== dependencies: postcss-selector-parser "^7.0.0" "@npmcli/redact@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@npmcli/redact/-/redact-3.0.0.tgz" integrity sha512-/1uFzjVcfzqrgCeGW7+SZ4hv0qLWmKXVzFahZGJ6QuJBj6Myt9s17+JL86i76NV9YSnJRcGXJYQbAU0rn1YTCQ== "@npmcli/redact@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@npmcli/redact/-/redact-4.0.0.tgz#c91121e02b7559a997614a2c1057cd7fc67608c4" integrity sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q== "@npmcli/run-script@^10.0.0", "@npmcli/run-script@^10.0.4": version "10.0.4" resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-10.0.4.tgz#99cddae483ce3dbf1a10f5683a4e6aaa02345ac0" integrity sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg== dependencies: "@npmcli/node-gyp" "^5.0.0" "@npmcli/package-json" "^7.0.0" "@npmcli/promise-spawn" "^9.0.0" node-gyp "^12.1.0" proc-log "^6.0.0" "@npmcli/run-script@^9.0.0": version "9.0.2" resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.0.2.tgz" integrity sha512-cJXiUlycdizQwvqE1iaAb4VRUM3RX09/8q46zjvy+ct9GhfZRWd7jXYVc1tn/CfRlGPVkX/u4sstRlepsm7hfw== dependencies: "@npmcli/node-gyp" "^4.0.0" "@npmcli/package-json" "^6.0.0" "@npmcli/promise-spawn" "^8.0.0" node-gyp "^11.0.0" proc-log "^5.0.0" which "^5.0.0" "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@sigstore/bundle@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.0.0.tgz" integrity sha512-XDUYX56iMPAn/cdgh/DTJxz5RWmqKV4pwvUAEKEWJl+HzKdCd/24wUa9JYNMlDSCb7SUHAdtksxYX779Nne/Zg== dependencies: "@sigstore/protobuf-specs" "^0.3.2" "@sigstore/bundle@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-4.0.0.tgz#854eda43eb6a59352037e49000177c8904572f83" integrity sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A== dependencies: "@sigstore/protobuf-specs" "^0.5.0" "@sigstore/core@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz" integrity sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg== "@sigstore/core@^3.1.0", "@sigstore/core@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@sigstore/core/-/core-3.2.0.tgz#beaea6ea4d7d4caadadb7453168e35636b78830e" integrity sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA== "@sigstore/protobuf-specs@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz#7dd46d68b76c322873a2ef7581ed955af6f4dcde" integrity sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ== "@sigstore/protobuf-specs@^0.5.0": version "0.5.1" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz#5401e444b6ab0db7d1969c91c43e7954927a52fe" integrity sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g== "@sigstore/sign@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@sigstore/sign/-/sign-3.0.0.tgz" integrity sha512-UjhDMQOkyDoktpXoc5YPJpJK6IooF2gayAr5LvXI4EL7O0vd58okgfRcxuaH+YTdhvb5aa1Q9f+WJ0c2sVuYIw== dependencies: "@sigstore/bundle" "^3.0.0" "@sigstore/core" "^2.0.0" "@sigstore/protobuf-specs" "^0.3.2" make-fetch-happen "^14.0.1" proc-log "^5.0.0" promise-retry "^2.0.1" "@sigstore/sign@^4.1.0": version "4.1.1" resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-4.1.1.tgz#34765fe4a190d693340c0771a3d150a397bcfc55" integrity sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ== dependencies: "@gar/promise-retry" "^1.0.2" "@sigstore/bundle" "^4.0.0" "@sigstore/core" "^3.2.0" "@sigstore/protobuf-specs" "^0.5.0" make-fetch-happen "^15.0.4" proc-log "^6.1.0" "@sigstore/tuf@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.0.0.tgz" integrity sha512-9Xxy/8U5OFJu7s+OsHzI96IX/OzjF/zj0BSSaWhgJgTqtlBhQIV2xdrQI5qxLD7+CWWDepadnXAxzaZ3u9cvRw== dependencies: "@sigstore/protobuf-specs" "^0.3.2" tuf-js "^3.0.1" "@sigstore/tuf@^4.0.1", "@sigstore/tuf@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-4.0.2.tgz#7d2fa2abcd5afa5baf752671d14a1c6ed0ed3196" integrity sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ== dependencies: "@sigstore/protobuf-specs" "^0.5.0" tuf-js "^4.1.0" "@sigstore/verify@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@sigstore/verify/-/verify-2.0.0.tgz" integrity sha512-Ggtq2GsJuxFNUvQzLoXqRwS4ceRfLAJnrIHUDrzAD0GgnOhwujJkKkxM/s5Bako07c3WtAs/sZo5PJq7VHjeDg== dependencies: "@sigstore/bundle" "^3.0.0" "@sigstore/core" "^2.0.0" "@sigstore/protobuf-specs" "^0.3.2" "@sigstore/verify@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@sigstore/verify/-/verify-3.1.0.tgz#4046d4186421db779501fe87fa5acaa5d4d21b08" integrity sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag== dependencies: "@sigstore/bundle" "^4.0.0" "@sigstore/core" "^3.1.0" "@sigstore/protobuf-specs" "^0.5.0" "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== "@socket.io/component-emitter@~3.1.0": version "3.1.2" resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== "@tufjs/canonical-json@2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz" integrity sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA== "@tufjs/models@3.0.1": version "3.0.1" resolved "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz" integrity sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA== dependencies: "@tufjs/canonical-json" "2.0.0" minimatch "^9.0.5" "@tufjs/models@4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-4.1.0.tgz#494b39cf5e2f6855d80031246dd236d8086069b3" integrity sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww== dependencies: "@tufjs/canonical-json" "2.0.0" minimatch "^10.1.1" "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== "@types/cors@^2.8.12": version "2.8.17" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz" integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== dependencies: "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": version "22.10.2" resolved "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz" integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== dependencies: undici-types "~6.20.0" "@types/yauzl@^2.9.1": version "2.10.3" resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz" integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== dependencies: "@types/node" "*" abbrev@1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== abbrev@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== abbrev@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-4.0.0.tgz#ec933f0e27b6cd60e89b5c6b2a304af42209bb05" integrity sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA== accepts@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz" integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== dependencies: mime-types "^3.0.0" negotiator "^1.0.0" accepts@~1.3.4: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: mime-types "~2.1.34" negotiator "0.6.3" acorn@^8.15.0: version "8.16.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.3" resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz" integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: version "6.1.0" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== any-promise@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@~3.1.2: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" aproba@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== archive-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz" integrity sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA== dependencies: file-type "^4.2.0" archy@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-each@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz" integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== array-slice@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz" integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== asn1@^0.2.6: version "0.2.6" resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" async@^2.6.0: version "2.6.4" resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" async@^3.2.3, async@~3.2.0: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== axios@^0.21.1: version "0.21.4" resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base64id@2.0.0, base64id@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" bcryptjs@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz" integrity sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g== bin-links@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-6.0.0.tgz#0245114374463a694e161a1e65417e7939ab2eba" integrity sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w== dependencies: cmd-shim "^8.0.0" npm-normalize-package-bin "^5.0.0" proc-log "^6.0.0" read-cmd-shim "^6.0.0" write-file-atomic "^7.0.0" binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== binary-extensions@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-3.0.0.tgz" integrity sha512-X0RfwMgXPEesg6PCXzytQZt9Unh9gtc4SfeTNJvKifUL//Oegcc/Yf31z6hThNZ8dnD3Ir3wkHVN0eWrTvP5ww== bl@^1.0.0: version "1.2.3" resolved "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz" integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== dependencies: readable-stream "^2.3.5" safe-buffer "^5.1.1" body-parser@^2.2.1: version "2.2.2" resolved "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz" integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== dependencies: bytes "^3.1.2" content-type "^1.0.5" debug "^4.4.3" http-errors "^2.0.0" iconv-lite "^0.7.0" on-finished "^2.4.1" qs "^6.14.1" raw-body "^3.0.1" type-is "^2.0.1" body@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/body/-/body-5.1.0.tgz" integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== dependencies: continuable-cache "^0.3.1" error "^7.0.0" raw-body "~1.1.0" safe-json-parse "~1.0.1" brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" brace-expansion@^5.0.2: version "5.0.4" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz" integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== dependencies: balanced-match "^4.0.2" brace-expansion@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== dependencies: balanced-match "^4.0.2" braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== buffer-alloc@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz" integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== dependencies: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@^5.2.1: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" ieee754 "^1.1.13" buildcheck@~0.0.6: version "0.0.7" resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.7.tgz#07a5e76c10ead8fa67d9e4c587b68f49e8f29d61" integrity sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA== bytes@1: version "1.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz" integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== cacache@^19.0.0, cacache@^19.0.1: version "19.0.1" resolved "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz" integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ== dependencies: "@npmcli/fs" "^4.0.0" fs-minipass "^3.0.0" glob "^10.2.2" lru-cache "^10.0.1" minipass "^7.0.3" minipass-collect "^2.0.1" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" p-map "^7.0.2" ssri "^12.0.0" tar "^7.4.3" unique-filename "^4.0.0" cacache@^20.0.0, cacache@^20.0.1, cacache@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-20.0.4.tgz#9b547dc3db0c1f87cba6dbbff91fb17181b4bbb1" integrity sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA== dependencies: "@npmcli/fs" "^5.0.0" fs-minipass "^3.0.0" glob "^13.0.0" lru-cache "^11.1.0" minipass "^7.0.3" minipass-collect "^2.0.1" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" p-map "^7.0.2" ssri "^13.0.0" cacheable-request@^2.1.1: version "2.1.4" resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz" integrity sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ== dependencies: clone-response "1.0.2" get-stream "3.0.0" http-cache-semantics "3.8.1" keyv "3.0.0" lowercase-keys "1.0.0" normalize-url "2.0.1" responselike "1.0.2" call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz" integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== dependencies: es-errors "^1.3.0" function-bind "^1.1.2" call-bound@^1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz" integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== dependencies: call-bind-apply-helpers "^1.0.1" get-intrinsic "^1.2.6" camel-case@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz" integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== dependencies: no-case "^2.2.0" upper-case "^1.1.1" camelcase@^6.0.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== chalk@^1.0.0, chalk@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" has-ansi "^2.0.0" strip-ansi "^3.0.0" supports-color "^2.0.0" chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" chalk@^4.1.0, chalk@~4.1.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" chalk@^5.6.2: version "5.6.2" resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz" integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== chokidar@^3.5.2: version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" readdirp "~3.6.0" optionalDependencies: fsevents "~2.3.2" chokidar@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: readdirp "^4.0.1" chownr@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chownr@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz" integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== ci-info@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz" integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== ci-info@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== cidr-regex@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-5.0.4.tgz#3232b0f29fc87ead52e05525c93922e8cdfec5c2" integrity sha512-RqFFeOfTjrYz0UAb40jeDnrkOyIo1whw/Qsj/7fC+XArortgpoDUHwQidvuAMmGI8vPjhis6kJTwUhwGjmYhSQ== clean-css@^4.2.1: version "4.2.4" resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz" integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== dependencies: source-map "~0.6.0" cliui@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.1" wrap-ansi "^7.0.0" clone-response@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q== dependencies: mimic-response "^1.0.0" cluster-key-slot@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== cmd-shim@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-8.0.0.tgz#5be238f22f40faf3f7e8c92edc3f5d354f7657b2" integrity sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA== color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colors@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== commander@^14.0.3: version "14.0.3" resolved "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz" integrity sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw== commander@^2.19.0, commander@^2.20.0, commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== common-ancestor-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz#f1d361aea9236aad5b92a0ff5b9df1422dd360ff" integrity sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng== concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== content-disposition@^0.5.2: version "0.5.4" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" content-disposition@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz" integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== content-type@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== continuable-cache@^0.3.1: version "0.3.1" resolved "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz" integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== cookie-signature@^1.2.1: version "1.2.2" resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz" integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== cookie@^0.7.1, cookie@~0.7.2: version "0.7.2" resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== corifeus-builder@^2026.4.154: version "2026.4.154" resolved "https://registry.yarnpkg.com/corifeus-builder/-/corifeus-builder-2026.4.154.tgz#5d590babbef5644c32275e99a4e9b028ad91c842" integrity sha512-bd0XGe5csRzfkrvh/cHKi2npG3M8m3WlmiHsJkW3DohrPwbLcui4q9ImfB/yc3mIfJ6OLyXckGQgFiis/1CjpQ== dependencies: corifeus-utils "^2026.4.132" download "^8.0.0" extract-zip "^2.0.1" fs-extra "^11.3.4" github-api "^3.4.0" glob "^13.0.6" glob-promise "^6.0.7" grunt "^1.6.2" grunt-contrib-clean "^2.0.1" grunt-contrib-copy "^1.0.0" grunt-contrib-htmlmin "^3.1.0" grunt-contrib-watch "^1.1.0" jit-grunt "^0.10.0" lodash "^4.18.1" mkdirp "^3.0.1" mocha "^11.7.5" mz "^2.7.0" npm "^11.13.0" npm-check-updates "^22.0.1" should "^13.2.3" time-grunt "^2.0.0" yaml "^2.8.3" corifeus-utils@^2026.4.132: version "2026.4.132" resolved "https://registry.yarnpkg.com/corifeus-utils/-/corifeus-utils-2026.4.132.tgz#d97ad2fd88d7b0954c8efaceb27a974b93a5e12b" integrity sha512-Qv/7uADw4LPsndGLmOGV66F8+p6Z4IWw9z+IFCIkJi5m6TjbG50S5l6jcMupJCXF2KqeEGJL+CbZ2qoZ3f4NKw== dependencies: fs-extra "^11.3.4" ms "^2.1.3" mz "^2.7.0" timestring "^7.0.0" uuid "^14.0.0" corifeus-utils@^2026.4.135: version "2026.4.135" resolved "https://registry.yarnpkg.com/corifeus-utils/-/corifeus-utils-2026.4.135.tgz#c5cb74ff44bb08c9d26d441d08e054131a3962af" integrity sha512-QUvVZp2JarrKCIyZwtfNwTbf1vN76rOklp1hHChdcoK23dbh9+GoDW4/HK1RLg47vcCDfeSASYTfjKSEokp0mQ== dependencies: fs-extra "^11.3.4" ms "^2.1.3" mz "^2.7.0" timestring "^7.0.0" uuid "^14.0.0" cors@~2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== dependencies: object-assign "^4" vary "^1" cpu-features@~0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== dependencies: buildcheck "~0.0.6" nan "^2.19.0" cross-spawn@^7.0.0: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== date-time@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/date-time/-/date-time-1.1.0.tgz" integrity sha512-RrxZQ06cdKe7YQ5oqIxs3GMc7W3vXscy7Ds+aZIqmxA59QnVtTiCseA4jbzVUub9xCbo9GuYVZo0OrZLYXnnmw== dependencies: time-zone "^0.1.0" dateformat@~4.6.2: version "4.6.3" resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz" integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== debug@4, debug@^4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6: version "4.4.0" resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: ms "^2.1.3" debug@^2.2.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" debug@^3.1.0: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" debug@^4.4.0, debug@^4.4.3, debug@~4.4.1: version "4.4.3" resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" debug@~4.3.1, debug@~4.3.4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" decamelize@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz" integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== dependencies: mimic-response "^1.0.0" decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz" integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== dependencies: file-type "^5.2.0" is-stream "^1.1.0" tar-stream "^1.5.2" decompress-tarbz2@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz" integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== dependencies: decompress-tar "^4.1.0" file-type "^6.1.0" is-stream "^1.1.0" seek-bzip "^1.0.5" unbzip2-stream "^1.0.9" decompress-targz@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz" integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== dependencies: decompress-tar "^4.1.1" file-type "^5.2.0" is-stream "^1.1.0" decompress-unzip@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz" integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== dependencies: file-type "^3.8.0" get-stream "^2.2.0" pify "^2.3.0" yauzl "^2.4.2" decompress@^4.2.1: version "4.2.1" resolved "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz" integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== dependencies: decompress-tar "^4.0.0" decompress-tarbz2 "^4.0.0" decompress-targz "^4.0.0" decompress-unzip "^4.0.1" graceful-fs "^4.1.10" make-dir "^1.0.0" pify "^2.3.0" strip-dirs "^2.0.0" denque@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== detect-file@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz" integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== diff@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz" integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== diff@^8.0.2: version "8.0.4" resolved "https://registry.yarnpkg.com/diff/-/diff-8.0.4.tgz#4f5baf3188b9b2431117b962eb20ba330fadf696" integrity sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw== download@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/download/-/download-8.0.0.tgz" integrity sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA== dependencies: archive-type "^4.0.0" content-disposition "^0.5.2" decompress "^4.2.1" ext-name "^5.0.0" file-type "^11.1.0" filenamify "^3.0.0" get-stream "^4.1.0" got "^8.3.1" make-dir "^2.1.0" p-event "^2.1.0" pify "^4.0.1" dunder-proto@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" es-errors "^1.3.0" gopd "^1.2.0" duplexer3@^0.1.4: version "0.1.5" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz" integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== encodeurl@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== encoding@^0.1.13: version "0.1.13" resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" engine.io-parser@~5.2.1: version "5.2.3" resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz" integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== engine.io@~6.6.0: version "6.6.2" resolved "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz" integrity sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw== dependencies: "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "2.0.0" cookie "~0.7.2" cors "~2.8.5" debug "~4.3.1" engine.io-parser "~5.2.1" ws "~8.17.1" env-paths@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== err-code@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== error@^7.0.0: version "7.2.1" resolved "https://registry.npmjs.org/error/-/error-7.2.1.tgz" integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== dependencies: string-template "~0.2.1" es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz" integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== dependencies: es-errors "^1.3.0" escalade@^3.1.1: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== esprima@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== etag@^1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eventemitter2@~0.4.13: version "0.4.14" resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz" integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== exit@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz" integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== dependencies: homedir-polyfill "^1.0.1" exponential-backoff@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== express@^5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz" integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== dependencies: accepts "^2.0.0" body-parser "^2.2.1" content-disposition "^1.0.0" content-type "^1.0.5" cookie "^0.7.1" cookie-signature "^1.2.1" debug "^4.4.0" depd "^2.0.0" encodeurl "^2.0.0" escape-html "^1.0.3" etag "^1.8.1" finalhandler "^2.1.0" fresh "^2.0.0" http-errors "^2.0.0" merge-descriptors "^2.0.0" mime-types "^3.0.0" on-finished "^2.4.1" once "^1.4.0" parseurl "^1.3.3" proxy-addr "^2.0.7" qs "^6.14.0" range-parser "^1.2.1" router "^2.2.0" send "^1.1.0" serve-static "^2.2.0" statuses "^2.0.1" type-is "^2.0.1" vary "^1.1.2" ext-list@^2.0.0: version "2.2.2" resolved "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz" integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== dependencies: mime-db "^1.28.0" ext-name@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz" integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== dependencies: ext-list "^2.0.0" sort-keys-length "^1.0.0" extend@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: debug "^4.1.1" get-stream "^5.1.0" yauzl "^2.10.0" optionalDependencies: "@types/yauzl" "^2.9.1" fastest-levenshtein@^1.0.16: version "1.0.16" resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== faye-websocket@~0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz" integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== dependencies: websocket-driver ">=0.5.1" fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== dependencies: pend "~1.2.0" fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== figures@^1.0.0: version "1.7.0" resolved "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz" integrity sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ== dependencies: escape-string-regexp "^1.0.5" object-assign "^4.1.0" file-sync-cmp@^0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz" integrity sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA== file-type@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz" integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g== file-type@^3.8.0: version "3.9.0" resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz" integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA== file-type@^4.2.0: version "4.4.0" resolved "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz" integrity sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ== file-type@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz" integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== file-type@^6.1.0: version "6.2.0" resolved "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz" integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== filename-reserved-regex@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz" integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== filenamify@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/filenamify/-/filenamify-3.0.0.tgz" integrity sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g== dependencies: filename-reserved-regex "^2.0.0" strip-outer "^1.0.0" trim-repeated "^1.0.0" fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" finalhandler@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz" integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== dependencies: debug "^4.4.0" encodeurl "^2.0.0" escape-html "^1.0.3" on-finished "^2.4.1" parseurl "^1.3.3" statuses "^2.0.1" find-up@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" findup-sync@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz" integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== dependencies: detect-file "^1.0.0" is-glob "^4.0.0" micromatch "^4.0.2" resolve-dir "^1.0.1" findup-sync@~5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz" integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== dependencies: detect-file "^1.0.0" is-glob "^4.0.3" micromatch "^4.0.4" resolve-dir "^1.0.1" fined@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz" integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== dependencies: expand-tilde "^2.0.2" is-plain-object "^2.0.3" object.defaults "^1.1.0" object.pick "^1.2.0" parse-filepath "^1.0.1" flagged-respawn@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz" integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== flat@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== follow-redirects@^1.14.0: version "1.15.9" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== for-in@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== for-own@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz" integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== dependencies: for-in "^1.0.1" foreground-child@^3.1.0: version "3.3.0" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz" integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fresh@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== from2@^2.1.1: version "2.3.0" resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== dependencies: inherits "^2.0.1" readable-stream "^2.0.0" fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^11.3.4: version "11.3.4" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.4.tgz#ab6934eca8bcf6f7f6b82742e33591f86301d6fc" integrity sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== dependencies: minipass "^3.0.0" fs-minipass@^3.0.0, fs-minipass@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz" integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== dependencies: minipass "^7.0.3" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== gaze@^1.1.0: version "1.1.3" resolved "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz" integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== dependencies: globule "^1.0.0" get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: version "1.2.6" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz" integrity sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA== dependencies: call-bind-apply-helpers "^1.0.1" dunder-proto "^1.0.0" es-define-property "^1.0.1" es-errors "^1.3.0" es-object-atoms "^1.0.0" function-bind "^1.1.2" gopd "^1.2.0" has-symbols "^1.1.0" hasown "^2.0.2" math-intrinsics "^1.0.0" get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz" integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== get-stream@^2.2.0: version "2.3.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz" integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== dependencies: object-assign "^4.0.1" pinkie-promise "^2.0.0" get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" get-stream@^5.1.0: version "5.2.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" getobject@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz" integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== github-api@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/github-api/-/github-api-3.4.0.tgz" integrity sha512-2yYqYS6Uy4br1nw0D3VrlYWxtGTkUhIZrumBrcBwKdBOzMT8roAe8IvI6kjIOkxqxapKR5GkEsHtz3Du/voOpA== dependencies: axios "^0.21.1" debug "^2.2.0" js-base64 "^2.1.9" utf8 "^2.1.1" glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-promise@^6.0.7: version "6.0.7" resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.7.tgz" integrity sha512-DEAe6br1w8ZF+y6KM2pzgdfhpreladtNvyNNVgSkxxkFWzXTJFXxQrJQQbAnc7kL0EUd7w5cR8u4K0P4+/q+Gw== glob@^10.2.2, glob@^10.3.10, glob@^10.3.7, glob@^10.4.5: version "10.4.5" resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" minimatch "^9.0.4" minipass "^7.1.2" package-json-from-dist "^1.0.0" path-scurry "^1.11.1" glob@^13.0.0, glob@^13.0.6: version "13.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== dependencies: minimatch "^10.2.2" minipass "^7.1.3" path-scurry "^2.0.2" glob@^7.1.3: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" glob@~7.1.1, glob@~7.1.6: version "7.1.7" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" global-modules@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz" integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== dependencies: global-prefix "^1.0.1" is-windows "^1.0.1" resolve-dir "^1.0.0" global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz" integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== dependencies: expand-tilde "^2.0.2" homedir-polyfill "^1.0.1" ini "^1.3.4" is-windows "^1.0.1" which "^1.2.14" globule@^1.0.0: version "1.3.4" resolved "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz" integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== dependencies: glob "~7.1.1" lodash "^4.17.21" minimatch "~3.0.2" gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== got@^8.3.1: version "8.3.2" resolved "https://registry.npmjs.org/got/-/got-8.3.2.tgz" integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== dependencies: "@sindresorhus/is" "^0.7.0" cacheable-request "^2.1.1" decompress-response "^3.3.0" duplexer3 "^0.1.4" get-stream "^3.0.0" into-stream "^3.1.0" is-retry-allowed "^1.1.0" isurl "^1.0.0-alpha5" lowercase-keys "^1.0.0" mimic-response "^1.0.0" p-cancelable "^0.4.0" p-timeout "^2.0.1" pify "^3.0.0" safe-buffer "^5.1.1" timed-out "^4.0.1" url-parse-lax "^3.0.0" url-to-options "^1.0.1" graceful-fs@^4.1.10, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== groq-sdk@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/groq-sdk/-/groq-sdk-1.1.2.tgz" integrity sha512-CZO0XUQQDhn43ri1+lZHxZKpb+bGutgTvFmCJtooexiitGmPqhm1hntOT3nCoaq07e+OpeokVnfUs0i/oQuUaQ== grunt-cli@^1.4.3: version "1.5.0" resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.5.0.tgz#24fa92225946b2002c535c7583a003e15203876f" integrity sha512-rILKAFoU0dzlf22SUfDtq2R1fosChXXlJM5j7wI6uoW8gwmXDXzbUvirlKZSYCdXl3LXFbR+8xyS+WFo+b6vlA== dependencies: grunt-known-options "~2.0.0" interpret "~1.1.0" liftup "~3.0.1" nopt "~5.0.0" v8flags "^4.0.1" grunt-contrib-clean@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz" integrity sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA== dependencies: async "^3.2.3" rimraf "^2.6.2" grunt-contrib-copy@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz" integrity sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA== dependencies: chalk "^1.1.1" file-sync-cmp "^0.1.0" grunt-contrib-htmlmin@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/grunt-contrib-htmlmin/-/grunt-contrib-htmlmin-3.1.0.tgz" integrity sha512-Khaa+0MUuqqNroDIe9tsjZkioZnW2Y+iTGbonBkLWaG7+SkSFExfb4jLt7M6rxKV3RSqlS7NtVvu4SVIPkmKXg== dependencies: chalk "^2.4.2" html-minifier "^4.0.0" pretty-bytes "^5.1.0" grunt-contrib-watch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz" integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== dependencies: async "^2.6.0" gaze "^1.1.0" lodash "^4.17.10" tiny-lr "^1.1.1" grunt-known-options@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz" integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== grunt-legacy-log-utils@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz" integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== dependencies: chalk "~4.1.0" lodash "~4.17.19" grunt-legacy-log@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz" integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== dependencies: colors "~1.1.2" grunt-legacy-log-utils "~2.1.0" hooker "~0.2.3" lodash "~4.17.19" grunt-legacy-util@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz" integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== dependencies: async "~3.2.0" exit "~0.1.2" getobject "~1.0.0" hooker "~0.2.3" lodash "~4.17.21" underscore.string "~3.3.5" which "~2.0.2" grunt@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.2.tgz#7d8223bf49819dcdfd53075393aa8c9d80cfe38e" integrity sha512-bUzh5nA/P5L66ihXTDP6J5BGnMB/8lXJXejYWSbH4Y4TvWM9t2S39sggQDYYQlx06cYcCsmu63HMYHGCIzUVfg== dependencies: dateformat "~4.6.2" eventemitter2 "~0.4.13" exit "~0.1.2" findup-sync "~5.0.0" glob "~7.1.6" grunt-cli "^1.4.3" grunt-known-options "~2.0.0" grunt-legacy-log "~3.0.0" grunt-legacy-util "~2.0.1" iconv-lite "~0.6.3" js-yaml "~3.14.0" minimatch "^3.1.5" nopt "^5.0.0" has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz" integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== dependencies: has-symbol-support-x "^1.4.1" hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" he@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz" integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== dependencies: parse-passwd "^1.0.0" hooker@^0.2.3, hooker@~0.2.3: version "0.2.3" resolved "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== hosted-git-info@^8.0.0: version "8.0.2" resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz" integrity sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg== dependencies: lru-cache "^10.0.1" hosted-git-info@^9.0.0, hosted-git-info@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-9.0.2.tgz#b38c8a802b274e275eeeccf9f4a1b1a0a8557ada" integrity sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg== dependencies: lru-cache "^11.1.0" html-minifier@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz" integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== dependencies: camel-case "^3.0.0" clean-css "^4.2.1" commander "^2.19.0" he "^1.2.0" param-case "^2.1.1" relateurl "^0.2.7" uglify-js "^3.5.1" http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz" integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: depd "~2.0.0" inherits "~2.0.4" setprototypeof "~1.2.0" statuses "~2.0.2" toidentifier "~1.0.1" http-parser-js@>=0.5.1: version "0.5.10" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== http-proxy-agent@^7.0.0: version "7.0.2" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== dependencies: agent-base "^7.1.0" debug "^4.3.4" https-proxy-agent@^7.0.1: version "7.0.6" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: agent-base "^7.1.2" debug "4" iconv-lite@^0.6.2, iconv-lite@~0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" iconv-lite@^0.7.0, iconv-lite@^0.7.2, iconv-lite@~0.7.0: version "0.7.2" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz" integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" ieee754@^1.1.13: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== ignore-walk@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz" integrity sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ== dependencies: minimatch "^9.0.0" ignore-walk@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-8.0.0.tgz#380c173badc3a18c57ff33440753f0052f572b14" integrity sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A== dependencies: minimatch "^10.0.3" imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" inherits@2, inherits@^2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@^1.3.4: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ini@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz" integrity sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw== ini@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-6.0.0.tgz#efc7642b276f6a37d22fdf56ef50889d7146bf30" integrity sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ== init-package-json@^8.2.5: version "8.2.5" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-8.2.5.tgz#6e90972b632eb410637a5a532019240ee7227d62" integrity sha512-IknQ+upLuJU6t3p0uo9wS3GjFD/1GtxIwcIGYOWR8zL2HxQeJwvxYTgZr9brJ8pyZ4kvpkebM8ZKcyqOeLOHSg== dependencies: "@npmcli/package-json" "^7.0.0" npm-package-arg "^13.0.0" promzard "^3.0.1" read "^5.0.1" semver "^7.7.2" validate-npm-package-name "^7.0.0" interpret@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz" integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== into-stream@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz" integrity sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ== dependencies: from2 "^2.1.1" p-is-promise "^1.1.0" ioredis@^5.10.1: version "5.10.1" resolved "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz" integrity sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA== dependencies: "@ioredis/commands" "1.5.1" cluster-key-slot "^1.1.0" debug "^4.3.4" denque "^2.1.0" lodash.defaults "^4.2.0" lodash.isarguments "^3.1.0" redis-errors "^1.2.0" redis-parser "^3.0.0" standard-as-callback "^2.1.0" ip-address@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz" integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== dependencies: jsbn "1.1.0" sprintf-js "^1.1.3" ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz" integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== dependencies: is-relative "^1.0.0" is-windows "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-cidr@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-6.0.4.tgz#7dcbde8640cf00cddc38a3c159d937dc216deb5c" integrity sha512-tOIBU3QiXy0W4LvHbcKWAWSuQfGwDiEILphFCAZtDqj7C57uv3ClO6K8aNEGV4VTA7bWJlpQ0suKQkUe6Rd6ag== dependencies: cidr-regex "^5.0.4" is-core-module@^2.16.0: version "2.16.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-finite@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz" integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz" integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-object@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-promise@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== is-relative@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz" integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== dependencies: is-unc-path "^1.0.0" is-retry-allowed@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-unc-path@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz" integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== dependencies: unc-path-regex "^0.1.2" is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isexe@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz" integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== isexe@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-4.0.0.tgz#48f6576af8e87a18feb796b7ed5e2e5903b43dca" integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw== isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz" integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== dependencies: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz" integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" jit-grunt@^0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/jit-grunt/-/jit-grunt-0.10.0.tgz" integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== js-base64@^2.1.9: version "2.6.4" resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" js-yaml@~3.14.0: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" jsbn@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== json-buffer@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz" integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== json-parse-even-better-errors@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz" integrity sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA== json-parse-even-better-errors@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz#93c89f529f022e5dadc233409324f0167b1e903e" integrity sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ== json-stringify-nice@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz" integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== just-diff-apply@^5.2.0: version "5.5.0" resolved "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz" integrity sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw== just-diff@^6.0.0: version "6.0.2" resolved "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz" integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA== keyv@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz" integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== dependencies: json-buffer "3.0.0" kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== libnpmaccess@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-10.0.3.tgz#856dc29fd35050159dff0039337aab503367586b" integrity sha512-JPHTfWJxIK+NVPdNMNGnkz4XGX56iijPbe0qFWbdt68HL+kIvSzh+euBL8npLZvl2fpaxo+1eZSdoG15f5YdIQ== dependencies: npm-package-arg "^13.0.0" npm-registry-fetch "^19.0.0" libnpmdiff@^8.1.6: version "8.1.6" resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-8.1.6.tgz#02db3eb234b52838cc0c69a18cc77936b53a6898" integrity sha512-nr6/MrxRnqMUoB9t0aHImBKArkJCU3YeaTyu817XYQXAQq9iWgX+ZVLgd+5wZVfoyemPdJj2LasXhFNyVk5GAA== dependencies: "@npmcli/arborist" "^9.4.3" "@npmcli/installed-package-contents" "^4.0.0" binary-extensions "^3.0.0" diff "^8.0.2" minimatch "^10.0.3" npm-package-arg "^13.0.0" pacote "^21.0.2" tar "^7.5.1" libnpmexec@^10.2.6: version "10.2.6" resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-10.2.6.tgz#b982a017650b986f4d7ee58756f0dff86a39e756" integrity sha512-aUHRHUhoi98CW9x+0+RzOVvKvl4rvGgr6o7wnWfdyuvZtU5WXGStfuArN1wBANxEP50bLTocMJrEsBktEuiVqw== dependencies: "@gar/promise-retry" "^1.0.0" "@npmcli/arborist" "^9.4.3" "@npmcli/package-json" "^7.0.0" "@npmcli/run-script" "^10.0.0" ci-info "^4.0.0" npm-package-arg "^13.0.0" pacote "^21.0.2" proc-log "^6.0.0" read "^5.0.1" semver "^7.3.7" signal-exit "^4.1.0" walk-up-path "^4.0.0" libnpmfund@^7.0.20: version "7.0.20" resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-7.0.20.tgz#a8f2a79b3bed8d6578f416d67363ef62df011206" integrity sha512-H1FvUdssvUlAfQJsNotf+DUetF2mS7d2sW8+MByLCMmgsZ+OkKbXgQit0PCjAwg8BD/Z/f8UO0FJT7bOYe73fQ== dependencies: "@npmcli/arborist" "^9.4.3" libnpmorg@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-8.0.1.tgz#975b61c2635f7edc07552ab8a455ce026decb88c" integrity sha512-/QeyXXg4hqMw0ESM7pERjIT2wbR29qtFOWIOug/xO4fRjS3jJJhoAPQNsnHtdwnCqgBdFpGQ45aIdFFZx2YhTA== dependencies: aproba "^2.0.0" npm-registry-fetch "^19.0.0" libnpmpack@^9.1.6: version "9.1.6" resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-9.1.6.tgz#f72985464c2eac91e10549402572e25c6a3ee31e" integrity sha512-Uov/MsMO+1MdJdT4PKdz6MiLNuZb73REKxbxKXKcNUaDkeBGNXxGB1GUxpdsvZlx1sos4MQDTYw34q4yw7hzHw== dependencies: "@npmcli/arborist" "^9.4.3" "@npmcli/run-script" "^10.0.0" npm-package-arg "^13.0.0" pacote "^21.0.2" libnpmpublish@^11.1.3: version "11.1.3" resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-11.1.3.tgz#fcda5c113798155fa111e04be63c9599d38ae4c2" integrity sha512-NVPTth/71cfbdYHqypcO9Lt5WFGTzFEcx81lWd7GDJIgZ95ERdYHGUfCtFejHCyqodKsQkNEx2JCkMpreDty/A== dependencies: "@npmcli/package-json" "^7.0.0" ci-info "^4.0.0" npm-package-arg "^13.0.0" npm-registry-fetch "^19.0.0" proc-log "^6.0.0" semver "^7.3.7" sigstore "^4.0.0" ssri "^13.0.0" libnpmsearch@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-9.0.1.tgz#674a88ffc9ab5826feb34c2c66e90797b38f4c2e" integrity sha512-oKw58X415ERY/BOGV3jQPVMcep8YeMRWMzuuqB0BAIM5VxicOU1tQt19ExCu4SV77SiTOEoziHxGEgJGw3FBYQ== dependencies: npm-registry-fetch "^19.0.0" libnpmteam@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-8.0.2.tgz#0417161bfcd155f5e8391cc2b6a05260ccbf1f41" integrity sha512-ypLrDUQoi8EhG+gzx5ENMcYq23YjPV17Mfvx4nOnQiHOi8vp47+4GvZBrMsEM4yeHPwxguF/HZoXH4rJfHdH/w== dependencies: aproba "^2.0.0" npm-registry-fetch "^19.0.0" libnpmversion@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/libnpmversion/-/libnpmversion-8.0.3.tgz#f50030c72a85e35b70a4ea4c075347f1999f9fe5" integrity sha512-Avj1GG3DT6MGzWOOk3yA7rORcMDUPizkIGbI8glHCO7WoYn3NYNmskLDwxg2NMY1Tyf2vrHAqTuSG58uqd1lJg== dependencies: "@npmcli/git" "^7.0.0" "@npmcli/run-script" "^10.0.0" json-parse-even-better-errors "^5.0.0" proc-log "^6.0.0" semver "^7.3.7" liftup@~3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz" integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== dependencies: extend "^3.0.2" findup-sync "^4.0.0" fined "^1.2.0" flagged-respawn "^1.0.1" is-plain-object "^2.0.4" object.map "^1.0.1" rechoir "^0.7.0" resolve "^1.19.0" livereload-js@^2.3.0: version "2.4.0" resolved "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz" integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash-es@^4.18.1: version "4.18.1" resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz" integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== lodash@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" is-unicode-supported "^0.1.0" lower-case@^1.1.1: version "1.1.4" resolved "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== lowercase-keys@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz" integrity sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A== lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1: version "11.3.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.3.tgz#d6c633c2a9657760fd30594d8d98da65330d9d78" integrity sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ== lz4js@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz" integrity sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg== make-dir@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" make-dir@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: pify "^4.0.1" semver "^5.6.0" make-fetch-happen@^14.0.0, make-fetch-happen@^14.0.1, make-fetch-happen@^14.0.3: version "14.0.3" resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz" integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ== dependencies: "@npmcli/agent" "^3.0.0" cacache "^19.0.1" http-cache-semantics "^4.1.1" minipass "^7.0.2" minipass-fetch "^4.0.0" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" negotiator "^1.0.0" proc-log "^5.0.0" promise-retry "^2.0.1" ssri "^12.0.0" make-fetch-happen@^15.0.0, make-fetch-happen@^15.0.1, make-fetch-happen@^15.0.4, make-fetch-happen@^15.0.5: version "15.0.5" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz#b0e3dd53d487b2733e4ea232c2bebf1bd16afb03" integrity sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg== dependencies: "@gar/promise-retry" "^1.0.0" "@npmcli/agent" "^4.0.0" "@npmcli/redact" "^4.0.0" cacache "^20.0.1" http-cache-semantics "^4.1.1" minipass "^7.0.2" minipass-fetch "^5.0.0" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" negotiator "^1.0.0" proc-log "^6.0.0" ssri "^13.0.0" make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz" integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== dependencies: kind-of "^6.0.2" map-cache@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== math-intrinsics@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== media-typer@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== merge-descriptors@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz" integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-db@^1.28.0: version "1.53.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== mime-db@^1.54.0: version "1.54.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== mime-types@^3.0.0, mime-types@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: mime-db "^1.54.0" mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== minimatch@^10.0.3, minimatch@^10.1.1, minimatch@^10.2.2, minimatch@^10.2.5: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: brace-expansion "^5.0.5" minimatch@^10.2.1: version "10.2.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz" integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== dependencies: brace-expansion "^5.0.2" minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" minimatch@^9.0.0, minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" minimatch@~3.0.2: version "3.0.8" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz" integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== dependencies: brace-expansion "^1.1.7" minipass-collect@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz" integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== dependencies: minipass "^7.0.3" minipass-fetch@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.0.tgz" integrity sha512-2v6aXUXwLP1Epd/gc32HAMIWoczx+fZwEPRHm/VwtrJzRGwR1qGZXEYV3Zp8ZjjbwaZhMrM6uHV4KVkk+XCc2w== dependencies: minipass "^7.0.3" minipass-sized "^1.0.3" minizlib "^3.0.1" optionalDependencies: encoding "^0.1.13" minipass-fetch@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-5.0.2.tgz#3973a605ddfd8abb865e50d6fc634853c8239729" integrity sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ== dependencies: minipass "^7.0.3" minipass-sized "^2.0.0" minizlib "^3.0.1" optionalDependencies: iconv-lite "^0.7.2" minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== dependencies: minipass "^3.0.0" minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" minipass-sized@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz" integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== dependencies: minipass "^3.0.0" minipass-sized@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-2.0.0.tgz#2228ee97e3f74f6b22ba6d1319addb7621534306" integrity sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA== dependencies: minipass "^7.1.2" minipass@^3.0.0: version "3.3.6" resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" minipass@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== minipass@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== minizlib@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: minipass "^3.0.0" yallist "^4.0.0" minizlib@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz" integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== dependencies: minipass "^7.0.4" rimraf "^5.0.5" minizlib@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== dependencies: minipass "^7.1.2" mkdirp@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mkdirp@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz" integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== mocha@^11.7.5: version "11.7.5" resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.5.tgz#58f5bbfa5e0211ce7e5ee6128107cefc2515a627" integrity sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig== dependencies: browser-stdout "^1.3.1" chokidar "^4.0.1" debug "^4.3.5" diff "^7.0.0" escape-string-regexp "^4.0.0" find-up "^5.0.0" glob "^10.4.5" he "^1.2.0" is-path-inside "^3.0.3" js-yaml "^4.1.0" log-symbols "^4.1.0" minimatch "^9.0.5" ms "^2.1.3" picocolors "^1.1.1" serialize-javascript "^6.0.2" strip-json-comments "^3.1.1" supports-color "^8.1.1" workerpool "^9.2.0" yargs "^17.7.2" yargs-parser "^21.1.1" yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@^2.1.1, ms@^2.1.2, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== mute-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-3.0.0.tgz#cd8014dd2acb72e1e91bb67c74f0019e620ba2d1" integrity sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw== mz@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: any-promise "^1.0.0" object-assign "^4.0.1" thenify-all "^1.0.0" nan@^2.19.0: version "2.26.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.26.2.tgz#2e5e25764224c737b9897790b57c3294d4dcee9c" integrity sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw== nan@^2.20.0: version "2.22.0" resolved "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz" integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== negotiator@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== no-case@^2.2.0: version "2.3.2" resolved "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz" integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== dependencies: lower-case "^1.1.1" node-gyp@^11.0.0: version "11.0.0" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-11.0.0.tgz" integrity sha512-zQS+9MTTeCMgY0F3cWPyJyRFAkVltQ1uXm+xXu/ES6KFgC6Czo1Seb9vQW2wNxSX2OrDTiqL0ojtkFxBQ0ypIw== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" glob "^10.3.10" graceful-fs "^4.2.6" make-fetch-happen "^14.0.3" nopt "^8.0.0" proc-log "^5.0.0" semver "^7.3.5" tar "^7.4.3" which "^5.0.0" node-gyp@^12.1.0: version "12.2.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-12.2.0.tgz#ff73f6f509e33d8b7e768f889ffc9738ad117b07" integrity sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" graceful-fs "^4.2.6" make-fetch-happen "^15.0.0" nopt "^9.0.0" proc-log "^6.0.0" semver "^7.3.5" tar "^7.5.4" tinyglobby "^0.2.12" which "^6.0.0" node-gyp@^12.3.0: version "12.3.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-12.3.0.tgz#a0e0d9364779451eaf4148b6f9a7366f98000b3f" integrity sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" graceful-fs "^4.2.6" nopt "^9.0.0" proc-log "^6.0.0" semver "^7.3.5" tar "^7.5.4" tinyglobby "^0.2.12" undici "^6.25.0" which "^6.0.0" nodemon@^3.1.14: version "3.1.14" resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz" integrity sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw== dependencies: chokidar "^3.5.2" debug "^4" ignore-by-default "^1.0.1" minimatch "^10.2.1" pstree.remy "^1.1.8" semver "^7.5.3" simple-update-notifier "^2.0.0" supports-color "^5.5.0" touch "^3.1.0" undefsafe "^2.0.5" nopt@^5.0.0, nopt@~5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== dependencies: abbrev "1" nopt@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/nopt/-/nopt-8.0.0.tgz" integrity sha512-1L/fTJ4UmV/lUxT2Uf006pfZKTvAgCF+chz+0OgBHO8u2Z67pE7AaAUUj7CJy0lXqHmymUvGFt6NE9R3HER0yw== dependencies: abbrev "^2.0.0" nopt@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-9.0.0.tgz#6bff0836b2964d24508b6b41b5a9a49c4f4a1f96" integrity sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw== dependencies: abbrev "^4.0.0" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz" integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== dependencies: prepend-http "^2.0.0" query-string "^5.0.1" sort-keys "^2.0.0" npm-audit-report@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-7.0.0.tgz#c384ac4afede55f21b30778202ad568e54644c35" integrity sha512-bluLL4xwGr/3PERYz50h2Upco0TJMDcLcymuFnfDWeGO99NqH724MNzhWi5sXXuXf2jbytFF0LyR8W+w1jTI6A== npm-bundled@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz" integrity sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA== dependencies: npm-normalize-package-bin "^4.0.0" npm-bundled@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-5.0.0.tgz#5025d847cfd06c7b8d9432df01695d0133d9ee80" integrity sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw== dependencies: npm-normalize-package-bin "^5.0.0" npm-check-updates@^22.0.1: version "22.0.1" resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-22.0.1.tgz#c4d55d0e8508e494fdbc643b9c82fefb47daac2b" integrity sha512-K8PDu7l9v7UKIwDSxLnqA9LHT76Mu4eCjGjp0JwSeSsyKWmX/YZY+AoBxw4oVdKwQLthWbzg1g+OKysHYGQCjQ== npm-install-checks@^7.1.0: version "7.1.1" resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz" integrity sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg== dependencies: semver "^7.1.1" npm-install-checks@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-8.0.0.tgz#f5d18e909bb8318d85093e9d8f36ac427c1cbe30" integrity sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA== dependencies: semver "^7.1.1" npm-normalize-package-bin@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz" integrity sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w== npm-normalize-package-bin@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz#2b207ff260f2e525ddce93356614e2f736728f89" integrity sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag== npm-package-arg@^12.0.0: version "12.0.1" resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.1.tgz" integrity sha512-aDxjFfPV3Liw0WOBWlyZLMBqtbgbg03rmGvHDJa2Ttv7tIz+1oB5qWec4psCDFZcZi9b5XdGkPdQiJxOPzvQRQ== dependencies: hosted-git-info "^8.0.0" proc-log "^5.0.0" semver "^7.3.5" validate-npm-package-name "^6.0.0" npm-package-arg@^13.0.0, npm-package-arg@^13.0.2: version "13.0.2" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-13.0.2.tgz#72a80f2afe8329860e63854489415e9e9a2f78a7" integrity sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA== dependencies: hosted-git-info "^9.0.0" proc-log "^6.0.0" semver "^7.3.5" validate-npm-package-name "^7.0.0" npm-packlist@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.0.tgz" integrity sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg== dependencies: ignore-walk "^7.0.0" npm-packlist@^10.0.1: version "10.0.4" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-10.0.4.tgz#aa2e0e4daf910eae8c5745c2645cf8bb8813de01" integrity sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng== dependencies: ignore-walk "^8.0.0" proc-log "^6.0.0" npm-pick-manifest@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz" integrity sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ== dependencies: npm-install-checks "^7.1.0" npm-normalize-package-bin "^4.0.0" npm-package-arg "^12.0.0" semver "^7.3.5" npm-pick-manifest@^11.0.1, npm-pick-manifest@^11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz#76cf6593a351849006c36b38a7326798e2a76d13" integrity sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ== dependencies: npm-install-checks "^8.0.0" npm-normalize-package-bin "^5.0.0" npm-package-arg "^13.0.0" semver "^7.3.5" npm-profile@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-12.0.1.tgz#f5aa0d931a4a75013a7521c86c30048e497310de" integrity sha512-Xs1mejJ1/9IKucCxdFMkiBJUre0xaxfCpbsO7DB7CadITuT4k68eI05HBlw4kj+Em1rsFMgeFNljFPYvPETbVQ== dependencies: npm-registry-fetch "^19.0.0" proc-log "^6.0.0" npm-registry-fetch@^18.0.0: version "18.0.2" resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz" integrity sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ== dependencies: "@npmcli/redact" "^3.0.0" jsonparse "^1.3.1" make-fetch-happen "^14.0.0" minipass "^7.0.2" minipass-fetch "^4.0.0" minizlib "^3.0.1" npm-package-arg "^12.0.0" proc-log "^5.0.0" npm-registry-fetch@^19.0.0, npm-registry-fetch@^19.1.1: version "19.1.1" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz#51e96d21f409a9bc4f96af218a8603e884459024" integrity sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw== dependencies: "@npmcli/redact" "^4.0.0" jsonparse "^1.3.1" make-fetch-happen "^15.0.0" minipass "^7.0.2" minipass-fetch "^5.0.0" minizlib "^3.0.1" npm-package-arg "^13.0.0" proc-log "^6.0.0" npm-user-validate@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-4.0.0.tgz#f3c7e8360e46c651dbaf2fc4eea8f66df51ae6df" integrity sha512-TP+Ziq/qPi/JRdhaEhnaiMkqfMGjhDLoh/oRfW+t5aCuIfJxIUxvwk6Sg/6ZJ069N/Be6gs00r+aZeJTfS9uHQ== npm@^11.13.0: version "11.13.0" resolved "https://registry.yarnpkg.com/npm/-/npm-11.13.0.tgz#1af5ccf2fc595e4ede1f46f4e6cda78cee0d7458" integrity sha512-cRmhaghDWA1lFgl3Ug4/VxDJdPBK/U+tNtnrl9kXunFqhWw1x4xL5txkNn7qzPuVfvXOmXyjHpMwsuk2uisbkg== dependencies: "@isaacs/string-locale-compare" "^1.1.0" "@npmcli/arborist" "^9.4.3" "@npmcli/config" "^10.8.1" "@npmcli/fs" "^5.0.0" "@npmcli/map-workspaces" "^5.0.3" "@npmcli/metavuln-calculator" "^9.0.3" "@npmcli/package-json" "^7.0.5" "@npmcli/promise-spawn" "^9.0.1" "@npmcli/redact" "^4.0.0" "@npmcli/run-script" "^10.0.4" "@sigstore/tuf" "^4.0.2" abbrev "^4.0.0" archy "~1.0.0" cacache "^20.0.4" chalk "^5.6.2" ci-info "^4.4.0" fastest-levenshtein "^1.0.16" fs-minipass "^3.0.3" glob "^13.0.6" graceful-fs "^4.2.11" hosted-git-info "^9.0.2" ini "^6.0.0" init-package-json "^8.2.5" is-cidr "^6.0.4" json-parse-even-better-errors "^5.0.0" libnpmaccess "^10.0.3" libnpmdiff "^8.1.6" libnpmexec "^10.2.6" libnpmfund "^7.0.20" libnpmorg "^8.0.1" libnpmpack "^9.1.6" libnpmpublish "^11.1.3" libnpmsearch "^9.0.1" libnpmteam "^8.0.2" libnpmversion "^8.0.3" make-fetch-happen "^15.0.5" minimatch "^10.2.5" minipass "^7.1.3" minipass-pipeline "^1.2.4" ms "^2.1.2" node-gyp "^12.3.0" nopt "^9.0.0" npm-audit-report "^7.0.0" npm-install-checks "^8.0.0" npm-package-arg "^13.0.2" npm-pick-manifest "^11.0.3" npm-profile "^12.0.1" npm-registry-fetch "^19.1.1" npm-user-validate "^4.0.0" p-map "^7.0.4" pacote "^21.5.0" parse-conflict-json "^5.0.1" proc-log "^6.1.0" qrcode-terminal "^0.12.0" read "^5.0.1" semver "^7.7.4" spdx-expression-parse "^4.0.0" ssri "^13.0.1" supports-color "^10.2.2" tar "^7.5.13" text-table "~0.2.0" tiny-relative-date "^2.0.2" treeverse "^3.0.0" validate-npm-package-name "^7.0.2" which "^6.0.1" number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz" integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== dependencies: array-each "^1.0.1" array-slice "^1.0.0" for-own "^1.0.0" isobject "^3.0.0" object.map@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz" integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== dependencies: for-own "^1.0.0" make-iterator "^1.0.0" object.pick@^1.2.0: version "1.3.0" resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== dependencies: isobject "^3.0.1" on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== p-event@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz" integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== dependencies: p-timeout "^2.0.1" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-is-promise@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz" integrity sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg== p-limit@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-map@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz" integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA== p-map@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8" integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== p-timeout@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz" integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== dependencies: p-finally "^1.0.0" package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== pacote@^21.0.0: version "21.0.0" resolved "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz" integrity sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA== dependencies: "@npmcli/git" "^6.0.0" "@npmcli/installed-package-contents" "^3.0.0" "@npmcli/package-json" "^6.0.0" "@npmcli/promise-spawn" "^8.0.0" "@npmcli/run-script" "^9.0.0" cacache "^19.0.0" fs-minipass "^3.0.0" minipass "^7.0.2" npm-package-arg "^12.0.0" npm-packlist "^10.0.0" npm-pick-manifest "^10.0.0" npm-registry-fetch "^18.0.0" proc-log "^5.0.0" promise-retry "^2.0.1" sigstore "^3.0.0" ssri "^12.0.0" tar "^6.1.11" pacote@^21.0.2, pacote@^21.5.0: version "21.5.0" resolved "https://registry.yarnpkg.com/pacote/-/pacote-21.5.0.tgz#475fe00db73585dec296590bec484109522e9e6f" integrity sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ== dependencies: "@gar/promise-retry" "^1.0.0" "@npmcli/git" "^7.0.0" "@npmcli/installed-package-contents" "^4.0.0" "@npmcli/package-json" "^7.0.0" "@npmcli/promise-spawn" "^9.0.0" "@npmcli/run-script" "^10.0.0" cacache "^20.0.0" fs-minipass "^3.0.0" minipass "^7.0.2" npm-package-arg "^13.0.0" npm-packlist "^10.0.1" npm-pick-manifest "^11.0.1" npm-registry-fetch "^19.0.0" proc-log "^6.0.0" sigstore "^4.0.0" ssri "^13.0.0" tar "^7.4.3" param-case@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz" integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== dependencies: no-case "^2.2.0" parse-conflict-json@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz#db4acd7472fb400c9808eb86611c2ff72f4c84ba" integrity sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ== dependencies: json-parse-even-better-errors "^5.0.0" just-diff "^6.0.0" just-diff-apply "^5.2.0" parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz" integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== dependencies: is-absolute "^1.0.0" map-cache "^0.2.0" path-root "^0.1.1" parse-ms@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz" integrity sha512-LpH1Cf5EYuVjkBvCDBYvkUPh+iv2bk3FHflxHkpCYT0/FZ1d3N3uJaLiHr4yGuMcFUhv6eAivitTvWZI4B/chg== parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== parseurl@^1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-root-regex@^0.1.0: version "0.1.2" resolved "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz" integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== path-root@^0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz" integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== dependencies: path-root-regex "^0.1.0" path-scurry@^1.11.1: version "1.11.1" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== dependencies: lru-cache "^11.0.0" minipass "^7.1.2" path-to-regexp@^8.0.0: version "8.3.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz" integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== pend@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== picomatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== pify@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pify@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== plur@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz" integrity sha512-qSnKBSZeDY8ApxwhfVIwKwF36KVJqb1/9nzYYq3j3vdwocULCXT8f8fQGkiw1Nk9BGfxiDagEe/pwakA+bOBqw== postcss-selector-parser@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f" integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== pretty-bytes@^5.1.0: version "5.6.0" resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== pretty-ms@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz" integrity sha512-H2enpsxzDhuzRl3zeSQpQMirn8dB0Z/gxW96j06tMfTviUWvX14gjKb7qd1gtkUyYhDPuoNe00K5PqNvy2oQNg== dependencies: is-finite "^1.0.1" parse-ms "^1.0.0" plur "^1.0.0" proc-log@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz" integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== proc-log@^6.0.0, proc-log@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-6.1.0.tgz#18519482a37d5198e231133a70144a50f21f0215" integrity sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== proggy@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/proggy/-/proggy-4.0.0.tgz#85fa89d7c81bc3fb77992a80f47bb1e17c610fa3" integrity sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ== promise-all-reject-late@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz" integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== promise-call-limit@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz" integrity sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw== promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz" integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== dependencies: err-code "^2.0.2" retry "^0.12.0" promzard@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/promzard/-/promzard-3.0.1.tgz#e42b9b75197661e5707dc7077da8dfd3bdfd9e3d" integrity sha512-M5mHhWh+Adz0BIxgSrqcc6GTCSconR7zWQV9vnOSptNtr6cSFlApLc28GbQhuN6oOWBQeV2C0bNE47JCY/zu3Q== dependencies: read "^5.0.0" proxy-addr@^2.0.7: version "2.0.7" resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: forwarded "0.2.0" ipaddr.js "1.9.1" pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== pump@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz" integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== dependencies: end-of-stream "^1.1.0" once "^1.3.1" qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== qs@^6.14.0, qs@^6.14.1: version "6.15.0" resolved "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz" integrity sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ== dependencies: side-channel "^1.1.0" qs@^6.4.0: version "6.13.1" resolved "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz" integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== dependencies: side-channel "^1.0.6" query-string@^5.0.1: version "5.1.1" resolved "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz" integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== dependencies: decode-uri-component "^0.2.0" object-assign "^4.1.0" strict-uri-encode "^1.0.0" randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" range-parser@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== raw-body@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz" integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== dependencies: bytes "~3.1.2" http-errors "~2.0.1" iconv-lite "~0.7.0" unpipe "~1.0.0" raw-body@~1.1.0: version "1.1.7" resolved "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz" integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== dependencies: bytes "1" string_decoder "0.10" read-cmd-shim@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz#98f5c8566e535829f1f8afb1595aaf05fd0f3970" integrity sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A== read@^5.0.0, read@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/read/-/read-5.0.1.tgz#e6b0a84743406182fdfc20b2418a11b39b7ef837" integrity sha512-+nsqpqYkkpet2UVPG8ZiuE8d113DK4vHYEoEhcrXBAlPiq6di7QRTuNiKQAbaRYegobuX2BpZ6QjanKOXnJdTA== dependencies: mute-stream "^3.0.0" readable-stream@^2.0.0, readable-stream@^2.3.0, readable-stream@^2.3.5: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" process-nextick-args "~2.0.0" safe-buffer "~5.1.1" string_decoder "~1.1.1" util-deprecate "~1.0.1" readdirp@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" rechoir@^0.7.0: version "0.7.1" resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz" integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== dependencies: resolve "^1.9.0" redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz" integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz" integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== dependencies: redis-errors "^1.0.0" relateurl@^0.2.7: version "0.2.7" resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz" integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== dependencies: expand-tilde "^2.0.0" global-modules "^1.0.0" resolve@^1.19.0, resolve@^1.9.0: version "1.22.10" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: is-core-module "^2.16.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" responselike@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz" integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== dependencies: lowercase-keys "^1.0.0" retry@^0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== rimraf@^2.6.2: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" rimraf@^5.0.5: version "5.0.10" resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz" integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== dependencies: glob "^10.3.7" router@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/router/-/router-2.2.0.tgz" integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== dependencies: debug "^4.4.0" depd "^2.0.0" is-promise "^4.0.0" parseurl "^1.3.3" path-to-regexp "^8.0.0" safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.1.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-json-parse@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz" integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== seek-bzip@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz" integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== dependencies: commander "^2.8.1" semver@^5.6.0: version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3: version "7.6.3" resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== semver@^7.7.2, semver@^7.7.4: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== send@^1.1.0, send@^1.2.0: version "1.2.1" resolved "https://registry.npmjs.org/send/-/send-1.2.1.tgz" integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== dependencies: debug "^4.4.3" encodeurl "^2.0.0" escape-html "^1.0.3" etag "^1.8.1" fresh "^2.0.0" http-errors "^2.0.1" mime-types "^3.0.2" ms "^2.1.3" on-finished "^2.4.1" range-parser "^1.2.1" statuses "^2.0.2" serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" serve-static@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz" integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== dependencies: encodeurl "^2.0.0" escape-html "^1.0.3" parseurl "^1.3.3" send "^1.2.0" setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== should-equal@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz" integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== dependencies: should-type "^1.4.0" should-format@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz" integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q== dependencies: should-type "^1.3.0" should-type-adaptors "^1.0.1" should-type-adaptors@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz" integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== dependencies: should-type "^1.3.0" should-util "^1.0.0" should-type@^1.3.0, should-type@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz" integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ== should-util@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz" integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== should@^13.2.3: version "13.2.3" resolved "https://registry.npmjs.org/should/-/should-13.2.3.tgz" integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== dependencies: should-equal "^2.0.0" should-format "^3.0.3" should-type "^1.4.0" should-type-adaptors "^1.0.1" should-util "^1.0.0" side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: es-errors "^1.3.0" object-inspect "^1.13.3" side-channel-map@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== dependencies: call-bound "^1.0.2" es-errors "^1.3.0" get-intrinsic "^1.2.5" object-inspect "^1.13.3" side-channel-weakmap@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== dependencies: call-bound "^1.0.2" es-errors "^1.3.0" get-intrinsic "^1.2.5" object-inspect "^1.13.3" side-channel-map "^1.0.1" side-channel@^1.0.6, side-channel@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: es-errors "^1.3.0" object-inspect "^1.13.3" side-channel-list "^1.0.0" side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== sigstore@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/sigstore/-/sigstore-3.0.0.tgz" integrity sha512-PHMifhh3EN4loMcHCz6l3v/luzgT3za+9f8subGgeMNjbJjzH4Ij/YoX3Gvu+kaouJRIlVdTHHCREADYf+ZteA== dependencies: "@sigstore/bundle" "^3.0.0" "@sigstore/core" "^2.0.0" "@sigstore/protobuf-specs" "^0.3.2" "@sigstore/sign" "^3.0.0" "@sigstore/tuf" "^3.0.0" "@sigstore/verify" "^2.0.0" sigstore@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-4.1.0.tgz#d34b92a544a05e003a2430209d26d8dfafd805a0" integrity sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA== dependencies: "@sigstore/bundle" "^4.0.0" "@sigstore/core" "^3.1.0" "@sigstore/protobuf-specs" "^0.5.0" "@sigstore/sign" "^4.1.0" "@sigstore/tuf" "^4.0.1" "@sigstore/verify" "^3.1.0" simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz" integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== dependencies: semver "^7.5.3" smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== snappyjs@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/snappyjs/-/snappyjs-0.7.0.tgz" integrity sha512-u5iEEXkMe2EInQio6Wv9LWHOQYRDbD2O9hzS27GpT/lwfIQhTCnHCTqedqHIHe9ZcvQo+9au6vngQayipz1NYw== socket.io-adapter@~2.5.2: version "2.5.5" resolved "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz" integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== dependencies: debug "~4.3.4" ws "~8.17.1" socket.io-parser@~4.2.4: version "4.2.4" resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz" integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" socket.io@^4.8.3: version "4.8.3" resolved "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz" integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== dependencies: accepts "~1.3.4" base64id "~2.0.0" cors "~2.8.5" debug "~4.4.1" engine.io "~6.6.0" socket.io-adapter "~2.5.2" socket.io-parser "~4.2.4" socks-proxy-agent@^8.0.3: version "8.0.5" resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz" integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== dependencies: agent-base "^7.1.2" debug "^4.3.4" socks "^2.8.3" socks@^2.8.3: version "2.8.3" resolved "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz" integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== dependencies: ip-address "^9.0.5" smart-buffer "^4.2.0" sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz" integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== dependencies: sort-keys "^1.0.0" sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== dependencies: is-plain-obj "^1.0.0" sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz" integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== dependencies: is-plain-obj "^1.0.0" source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" source-map@^0.6.0, source-map@~0.6.0: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz" integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: version "2.5.0" resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz" integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== spdx-expression-parse@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-expression-parse@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz" integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: version "3.0.23" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz#b069e687b1291a32f126893ed76a27a745ee2133" integrity sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw== sprintf-js@^1.1.1, sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== ssh2@^1.15.0: version "1.16.0" resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz" integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg== dependencies: asn1 "^0.2.6" bcrypt-pbkdf "^1.0.2" optionalDependencies: cpu-features "~0.0.10" nan "^2.20.0" ssri@^12.0.0: version "12.0.0" resolved "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz" integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ== dependencies: minipass "^7.0.3" ssri@^13.0.0, ssri@^13.0.1: version "13.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-13.0.1.tgz#2d8946614d33f4d0c84946bb370dce7a9379fd18" integrity sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ== dependencies: minipass "^7.0.3" standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz" integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== string-template@~0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" emoji-regex "^9.2.2" strip-ansi "^7.0.1" string_decoder@0.10: version "0.10.31" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: ansi-regex "^6.0.1" strip-dirs@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz" integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== dependencies: is-natural-number "^4.0.1" strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-outer@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz" integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== dependencies: escape-string-regexp "^1.0.2" supports-color@^10.2.2: version "10.2.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-10.2.2.tgz#466c2978cc5cd0052d542a0b576461c2b802ebb4" integrity sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g== supports-color@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz" integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== dependencies: bl "^1.0.0" buffer-alloc "^1.2.0" end-of-stream "^1.0.0" fs-constants "^1.0.0" readable-stream "^2.3.0" to-buffer "^1.1.1" xtend "^4.0.0" tar@^6.1.11: version "6.2.1" resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" tar@^7.4.3: version "7.4.3" resolved "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz" integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" minipass "^7.1.2" minizlib "^3.0.1" mkdirp "^3.0.1" yallist "^5.0.0" tar@^7.5.1, tar@^7.5.13, tar@^7.5.4: version "7.5.13" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" minipass "^7.1.2" minizlib "^3.1.0" yallist "^5.0.0" terser@^5.46.2: version "5.46.2" resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.2.tgz#b9529672d5b0024c7959571c83b82f65077b2a4f" integrity sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20" text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": version "3.3.1" resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" through@^2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== time-grunt@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/time-grunt/-/time-grunt-2.0.0.tgz" integrity sha512-iQD2AeDYCAJrsPC/eUsfYZD9UT7TuBOmUIgFV5zeTQgRk6yLJKoc3aYR0gusJ0m+bG13B6qrDZ0SwPLe0/htHw== dependencies: chalk "^1.0.0" date-time "^1.1.0" figures "^1.0.0" hooker "^0.2.3" number-is-nan "^1.0.0" pretty-ms "^2.1.0" text-table "^0.2.0" time-zone@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/time-zone/-/time-zone-0.1.0.tgz" integrity sha512-S5CjtVIkeBTnlsaZP3gjsTb78ClBe74sEcgEoBwAVUKnTRDAGqUtLLIZHMsIyqOWjt9DGQpLMMoD8ZKIfP2ddQ== timed-out@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz" integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== timestring@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/timestring/-/timestring-7.0.0.tgz" integrity sha512-U7ttxEdKWqHYJ96OGoJJR5gU8Nwkl3tlY0n7Jr4vcpLD2RkVZLE1Ph9k8ZRrZ7LYX9QCtd3M9OUaR9P8Z37QNg== tiny-lr@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz" integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== dependencies: body "^5.1.0" debug "^3.1.0" faye-websocket "~0.10.0" livereload-js "^2.3.0" object-assign "^4.1.0" qs "^6.4.0" tiny-relative-date@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-2.0.2.tgz#0c35c2a3ef87b80f311314918505aa86c2d44bc9" integrity sha512-rGxAbeL9z3J4pI2GtBEoFaavHdO4RKAU54hEuOef5kfx5aPqiQtbhYktMOTL5OA33db8BjsDcLXuNp+/v19PHw== tinyglobby@^0.2.12: version "0.2.16" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== dependencies: fdir "^6.5.0" picomatch "^4.0.4" to-buffer@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== touch@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz" integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== treeverse@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz" integrity sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ== trim-repeated@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz" integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== dependencies: escape-string-regexp "^1.0.2" tuf-js@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz" integrity sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA== dependencies: "@tufjs/models" "3.0.1" debug "^4.3.6" make-fetch-happen "^14.0.1" tuf-js@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-4.1.0.tgz#ae4ef9afa456fcb4af103dc50a43bc031f066603" integrity sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ== dependencies: "@tufjs/models" "4.1.0" debug "^4.4.3" make-fetch-happen "^15.0.1" tunnel-ssh@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-5.2.0.tgz" integrity sha512-IGiyhE2RSt3NVvZ7aKH3ykziAxKNPe/z97Rab/lrIXslif/cq7J/m6EXfERlDITiFyGGYMqqi5SSrt/mk1VbEg== dependencies: ssh2 "^1.15.0" tweetnacl@^0.14.3: version "0.14.5" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== type-is@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz" integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== dependencies: content-type "^1.0.5" media-typer "^1.1.0" mime-types "^3.0.0" uglify-js@^3.5.1: version "3.19.3" resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz" integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== dependencies: buffer "^5.2.1" through "^2.3.8" unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz" integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== underscore.string@~3.3.5: version "3.3.6" resolved "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz" integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== dependencies: sprintf-js "^1.1.1" util-deprecate "^1.0.2" undici-types@~6.20.0: version "6.20.0" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== undici@^6.25.0: version "6.25.0" resolved "https://registry.yarnpkg.com/undici/-/undici-6.25.0.tgz#8c4efb8c998dc187fc1cfb5dde1ef19a211849fb" integrity sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg== unique-filename@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz" integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ== dependencies: unique-slug "^5.0.0" unique-slug@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz" integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg== dependencies: imurmurhash "^0.1.4" universalify@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== upper-case@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz" integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== dependencies: prepend-http "^2.0.0" url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz" integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== utf8@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz" integrity sha512-QXo+O/QkLP/x1nyi54uQiG0XrODxdysuQvE5dtVqv7F5K2Qb6FsN+qbr6KhF5wQ20tfcV3VQp0/2x1e1MRSPWg== util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== uuid@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-14.0.0.tgz#0af883220163d264ffe0c084f6b8a89b9666966d" integrity sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg== v8flags@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-4.0.1.tgz#98fe6c4308317c5f394d85a435eb192490f7e132" integrity sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg== validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" validate-npm-package-name@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz" integrity sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg== validate-npm-package-name@^7.0.0, validate-npm-package-name@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz#e57c3d721a4c8bbff454a246e7f7da811559ea0d" integrity sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A== vary@^1, vary@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== walk-up-path@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz" integrity sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A== websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: http-parser-js ">=0.5.1" safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: version "0.1.4" resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" which@^2.0.1, which@~2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" which@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/which/-/which-5.0.0.tgz" integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ== dependencies: isexe "^3.1.1" which@^6.0.0, which@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/which/-/which-6.0.1.tgz#021642443a198fb93b784a5606721cb18cfcbfce" integrity sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg== dependencies: isexe "^4.0.0" workerpool@^9.2.0: version "9.3.4" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41" integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: ansi-styles "^6.1.0" string-width "^5.0.1" strip-ansi "^7.0.1" wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-7.0.1.tgz#0e2a450ab5aa306bcfcd3aed61833b10cc4fb885" integrity sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg== dependencies: signal-exit "^4.0.1" ws@~8.17.1: version "8.17.1" resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xtend@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^5.0.5: version "5.0.8" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yallist@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yaml@^2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d" integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" decamelize "^4.0.0" flat "^5.0.2" is-plain-obj "^2.1.0" yargs@^17.7.2: version "17.7.2" resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" yargs-parser "^21.1.1" yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==