diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/promptforge/.gitignore b/promptforge/.gitignore new file mode 100644 index 0000000..b811ba0 --- /dev/null +++ b/promptforge/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.env* +.promptforge/ +*.vsix +*.map +.DS_Store diff --git a/promptforge/.prettierrc b/promptforge/.prettierrc new file mode 100644 index 0000000..5a8bc50 --- /dev/null +++ b/promptforge/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "semi": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/promptforge/.vscodeignore b/promptforge/.vscodeignore new file mode 100644 index 0000000..137e0a5 --- /dev/null +++ b/promptforge/.vscodeignore @@ -0,0 +1,22 @@ +src/** +webview/src/** +webview/node_modules/** +tests/** +node_modules/** +.env* +.promptforge/** +*.map +*.vsix +.gitignore +.prettierrc +eslint.config.js +postcss.config.js +tailwind.config.js +tsconfig.json +tsconfig.webview.json +vitest.config.ts +esbuild.js +package-lock.json +!dist/** +!media/** +!package.json diff --git a/promptforge/CHANGELOG.md b/promptforge/CHANGELOG.md new file mode 100644 index 0000000..33fa696 --- /dev/null +++ b/promptforge/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## [0.1.0] - 2026-04-04 + +### Added +- AI co-writer with model-specific best practices (Claude, GPT-5, Gemini, o-series) +- Dynamic variable templates (`{{variable}}` and `{{variable:default}}`) +- Test runner with semantic scoring and iterative refinement +- A/B testing side by side (up to 20 inputs) +- Call replay with visual diff +- Cost tracking per model (10 models, 4 providers) +- Code snippet generator (TypeScript, Python, cURL x Anthropic, OpenAI, Google) +- Prompt library with versioning (max 15 versions + automatic archiving) +- Export in JSON, Markdown, and code snippets +- API key management via VS Code SecretStorage +- Onboarding banner for first-time users diff --git a/promptforge/LICENSE b/promptforge/LICENSE new file mode 100644 index 0000000..e7c33e3 --- /dev/null +++ b/promptforge/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 PromptForge Contributors + +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. diff --git a/promptforge/README.md b/promptforge/README.md new file mode 100644 index 0000000..4ef4737 --- /dev/null +++ b/promptforge/README.md @@ -0,0 +1,87 @@ +# PromptForge + +> AI-powered prompt engineering copilot for VS Code + +PromptForge is a VS Code extension that helps you write, test, and refine prompts for Large Language Models — with an AI co-writer that knows the best practices of each model. + +## Features + +### Co-Writer with Model-Specific Best Practices +Write prompts with an AI assistant that adapts its suggestions based on the target model: +- **Claude** (Anthropic): XML tag structure, role-first system prompts +- **GPT-5 / o-series** (OpenAI): Markdown headers, concise directives +- **Gemini** (Google): Declarative instructions, thinking level control + +### Dynamic Variable Templates +Use `{{variable}}` and `{{variable:default_value}}` syntax in your prompts. PromptForge auto-detects variables and generates input forms. + +### Test Runner with Semantic Scoring +Test your prompts against live models, compare outputs with desired results, and get a semantic similarity score. Refine prompts iteratively based on real outputs. + +### A/B Testing +Compare two prompt versions side by side with the same inputs. See cost, latency, and output differences at a glance. Up to 20 inputs per test. + +### Call Replay +Capture API calls and replay them later with different prompts. Compare original vs. new outputs with a visual diff. + +### Cost Tracking +Track token usage and costs per model with 4-decimal precision. Supports 10 models across 4 providers. + +### Code Snippet Export +Generate ready-to-use code in TypeScript, Python, or cURL for Anthropic, OpenAI, and Google APIs (9 combinations). + +### Prompt Library +Save, version, search, and export your prompts. Up to 15 versions per prompt with automatic archiving. + +## Supported Models + +| Provider | Models | +|----------|--------| +| Anthropic | Claude Opus 4.6, Claude Sonnet 4.6, Claude Haiku 4.5 | +| OpenAI | GPT-5.4, GPT-5.4 Mini, o3, o4-mini | +| Google | Gemini 3.1 Pro, Gemini 2.5 Flash, Gemini 3.1 Flash Lite | +| OpenRouter | Auto (fallback) | + +## Getting Started + +1. Install the extension from the VS Code Marketplace +2. Open Command Palette (`Ctrl+Shift+P`) and run `PromptForge: Configure API Keys` +3. Enter your API key for at least one provider +4. Press `Ctrl+Alt+N` to create your first prompt + +## Keyboard Shortcuts + +| Shortcut | Command | +|----------|---------| +| `Ctrl+Alt+N` | New Prompt | +| `Ctrl+Alt+T` | Test Prompt | +| `Ctrl+Alt+L` | Open Library | + +## Requirements + +- VS Code 1.100.0 or later +- At least one API key (Anthropic, OpenAI, Google, or OpenRouter) + +## Extension Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `promptforge.defaultModel` | `claude-opus-4-6` | Default model for new prompts | + +## Data Storage + +- **API Keys**: Stored securely in VS Code SecretStorage (never in files) +- **Prompts**: Saved as JSON in `.promptforge/prompts/` (add to `.gitignore`) +- **Replays**: Saved as JSON in `.promptforge/replays/` + +## Tech Stack + +- TypeScript (strict mode) +- React 18 + Tailwind CSS (Webview UI) +- Zustand (state management) +- esbuild (bundler) +- Vitest (testing) + +## License + +MIT diff --git a/promptforge/esbuild.js b/promptforge/esbuild.js new file mode 100644 index 0000000..8f9a68b --- /dev/null +++ b/promptforge/esbuild.js @@ -0,0 +1,59 @@ +const esbuild = require('esbuild') +const { execSync } = require('child_process') +const path = require('path') +const fs = require('fs') + +const production = process.argv.includes('--production') +const watch = process.argv.includes('--watch') + +const extensionConfig = { + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + platform: 'node', + outfile: 'dist/extension.js', + external: ['vscode'], + minify: production, + sourcemap: !production, +} + +const webviewConfig = { + entryPoints: ['webview/src/main.tsx'], + bundle: true, + format: 'iife', + platform: 'browser', + outfile: 'dist/webview/main.js', + loader: { '.tsx': 'tsx', '.ts': 'ts', '.css': 'css' }, + define: { + 'process.env.NODE_ENV': production ? '"production"' : '"development"', + }, + minify: production, + sourcemap: !production, +} + +function buildTailwindCSS() { + const inputCSS = path.resolve(__dirname, 'webview/src/index.css') + const outputCSS = path.resolve(__dirname, 'dist/webview/main.css') + fs.mkdirSync(path.dirname(outputCSS), { recursive: true }) + execSync( + `npx tailwindcss -i ${inputCSS} -o ${outputCSS} ${production ? '--minify' : ''}`, + { stdio: 'inherit' }, + ) +} + +async function main() { + buildTailwindCSS() + + const extCtx = await esbuild.context(extensionConfig) + const wvCtx = await esbuild.context(webviewConfig) + + if (watch) { + await Promise.all([extCtx.watch(), wvCtx.watch()]) + console.log('Watching...') + } else { + await Promise.all([extCtx.rebuild(), wvCtx.rebuild()]) + await Promise.all([extCtx.dispose(), wvCtx.dispose()]) + } +} + +main().catch(console.error) diff --git a/promptforge/eslint.config.js b/promptforge/eslint.config.js new file mode 100644 index 0000000..f3127ca --- /dev/null +++ b/promptforge/eslint.config.js @@ -0,0 +1,24 @@ +module.exports = [ + { + files: ['src/**/*.ts'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + rules: { + 'no-unused-vars': 'off', + 'no-undef': 'off', + }, + }, + { + files: ['webview/src/**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + rules: { + 'no-unused-vars': 'off', + 'no-undef': 'off', + }, + }, +] diff --git a/promptforge/media/icon.png b/promptforge/media/icon.png new file mode 100644 index 0000000..c08ede2 Binary files /dev/null and b/promptforge/media/icon.png differ diff --git a/promptforge/package-lock.json b/promptforge/package-lock.json new file mode 100644 index 0000000..c5103bc --- /dev/null +++ b/promptforge/package-lock.json @@ -0,0 +1,8287 @@ +{ + "name": "promptforge", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "promptforge", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.39.0", + "@google/generative-ai": "^0.21.0", + "openai": "^6.33.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@types/vscode": "^1.100.0", + "@vscode/test-electron": "^2.4.0", + "@vscode/vsce": "^3.0.0", + "autoprefixer": "^10.4.0", + "dotenv": "^16.0.0", + "esbuild": "^0.25.0", + "esbuild-postcss": "^0.0.4", + "eslint": "^9.0.0", + "postcss": "^8.4.0", + "prettier": "^3.4.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.7.0", + "vitest": "^2.0.0", + "zustand": "^5.0.0" + }, + "engines": { + "vscode": "^1.100.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", + "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.6.3.tgz", + "integrity": "sha512-sTjMtUm+bJpENU/1WlRzHEsgEHppZDZ1EtNyaOODg/sQBtMxxJzGB+MOCM+T2Q5Qe1fKBrdxUmjyRxm0r7Ez9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.4.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.4.1.tgz", + "integrity": "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.2.tgz", + "integrity": "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.4.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@google/generative-ai": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", + "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.2.tgz", + "integrity": "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.2.tgz", + "integrity": "sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.5.2", + "@textlint/resolver": "15.5.2", + "@textlint/types": "15.5.2", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.17.23", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.2.tgz", + "integrity": "sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.2.tgz", + "integrity": "sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.2.tgz", + "integrity": "sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.5.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.110.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz", + "integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz", + "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vscode/test-electron": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.7.1.tgz", + "integrity": "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz", + "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001785", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz", + "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild-postcss": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/esbuild-postcss/-/esbuild-postcss-0.0.4.tgz", + "integrity": "sha512-CKYibp+aCswskE+gBPnGZ0b9YyuY0n9w2dxyMaoLYEvGTwmjkRj5SV8l1zGJpw8KylqmcMTK0Gr349RnOLd+8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-load-config": "^3.1.0" + }, + "peerDependencies": { + "esbuild": "*", + "postcss": "^8.0.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "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==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "6.33.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.33.0.tgz", + "integrity": "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "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==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/promptforge/package.json b/promptforge/package.json new file mode 100644 index 0000000..9c62f06 --- /dev/null +++ b/promptforge/package.json @@ -0,0 +1,113 @@ +{ + "name": "promptforge", + "displayName": "PromptForge", + "description": "AI-powered prompt engineering copilot for VS Code", + "version": "0.1.0", + "publisher": "PromptForge", + "icon": "media/icon.png", + "repository": { + "type": "git", + "url": "https://github.com/Jonathannascinento/.github-workflows" + }, + "engines": { + "vscode": "^1.100.0" + }, + "categories": [ + "AI", + "Machine Learning", + "Other" + ], + "activationEvents": [ + "onCommand:promptforge.newPrompt", + "onCommand:promptforge.testPrompt", + "onCommand:promptforge.openLibrary", + "onCommand:promptforge.configureKeys" + ], + "main": "./dist/extension.js", + "contributes": { + "commands": [ + { + "command": "promptforge.newPrompt", + "title": "PromptForge: New Prompt", + "icon": "$(add)" + }, + { + "command": "promptforge.testPrompt", + "title": "PromptForge: Test Prompt", + "icon": "$(beaker)" + }, + { + "command": "promptforge.openLibrary", + "title": "PromptForge: Open Library", + "icon": "$(library)" + }, + { + "command": "promptforge.configureKeys", + "title": "PromptForge: Configure API Keys", + "icon": "$(key)" + } + ], + "keybindings": [ + { + "command": "promptforge.newPrompt", + "key": "ctrl+alt+n", + "mac": "cmd+alt+n" + }, + { + "command": "promptforge.testPrompt", + "key": "ctrl+alt+t", + "mac": "cmd+alt+t" + }, + { + "command": "promptforge.openLibrary", + "key": "ctrl+alt+l", + "mac": "cmd+alt+l" + } + ], + "configuration": { + "title": "PromptForge", + "properties": { + "promptforge.defaultModel": { + "type": "string", + "default": "claude-opus-4-6", + "description": "Default model for new prompts" + } + } + } + }, + "scripts": { + "build": "node esbuild.js --production", + "watch": "node esbuild.js --watch", + "lint": "eslint src/ webview/src/ --ext .ts,.tsx", + "format": "prettier --write \"src/**/*.ts\" \"webview/src/**/*.{ts,tsx}\"", + "test": "vitest run", + "test:watch": "vitest", + "package": "vsce package" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.39.0", + "openai": "^6.33.0", + "@google/generative-ai": "^0.21.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/vscode": "^1.100.0", + "@types/node": "^20.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@vscode/vsce": "^3.0.0", + "@vscode/test-electron": "^2.4.0", + "autoprefixer": "^10.4.0", + "esbuild": "^0.25.0", + "eslint": "^9.0.0", + "postcss": "^8.4.0", + "prettier": "^3.4.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.7.0", + "vitest": "^2.0.0", + "zustand": "^5.0.0", + "dotenv": "^16.0.0" + } +} diff --git a/promptforge/postcss.config.js b/promptforge/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/promptforge/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/promptforge/promptforge-0.1.0.vsix b/promptforge/promptforge-0.1.0.vsix new file mode 100644 index 0000000..1cee6ba Binary files /dev/null and b/promptforge/promptforge-0.1.0.vsix differ diff --git a/promptforge/src/bestPractices/claude.ts b/promptforge/src/bestPractices/claude.ts new file mode 100644 index 0000000..a4ffe84 --- /dev/null +++ b/promptforge/src/bestPractices/claude.ts @@ -0,0 +1,21 @@ +import { PromptGuidelines } from '../types/guidelines' + +export const claudeGuidelines: PromptGuidelines = { + preferredStructure: 'xml-tags', + systemPromptStyle: 'role-first-with-context', + reasoningControl: 'use-effort-level-or-extended-thinking', + boostPatterns: [ + 'wrap-sections-in-xml-tags', + 'place-examples-inside--tags', + 'explicit-output-format-specification', + 'role-definition-in-system-prompt', + ], + avoidPatterns: [ + 'vague-personas', + 'redundant-chain-of-thought-instructions', + 'sycophancy-inducing-language', + ], + examplePosition: 'before-instruction', + chainOfThoughtNote: + 'Claude ja pensa passo a passo. Instrua sobre formato de output, nao sobre o processo de raciocinio.', +} diff --git a/promptforge/src/bestPractices/cowriterSystemPrompt.ts b/promptforge/src/bestPractices/cowriterSystemPrompt.ts new file mode 100644 index 0000000..31ea9ae --- /dev/null +++ b/promptforge/src/bestPractices/cowriterSystemPrompt.ts @@ -0,0 +1,42 @@ +import { PromptGuidelines } from '../types/guidelines' + +export function buildCowriterSystemPrompt( + targetModel: string, + guidelines: PromptGuidelines, + userIntent: string, + userContext: string, + audience: string, + useCase: string, +): string { + return `Voce e um especialista senior em engenharia de prompts para Large Language Models. +Seu papel e co-escrever prompts e system prompts junto com o usuario, em tempo real, de forma colaborativa. + +MODELO ALVO: ${targetModel} + +DIRETRIZES DO MODELO ALVO: +- Estrutura preferida: ${guidelines.preferredStructure} +- Estilo de system prompt: ${guidelines.systemPromptStyle} +- Controle de raciocinio: ${guidelines.reasoningControl} +- Padroes a aplicar: ${guidelines.boostPatterns.join(', ')} +- Padroes a evitar: ${guidelines.avoidPatterns.join(', ')} +${guidelines.chainOfThoughtNote ? `- Nota sobre CoT: ${guidelines.chainOfThoughtNote}` : ''} +${guidelines.temperatureNote ? `- Nota critica sobre temperatura: ${guidelines.temperatureNote}` : ''} + +CONTEXTO DO USUARIO: +- Intencao: ${userIntent} +- Contexto: ${userContext} +- Audiencia do prompt: ${audience} +- Caso de uso: ${useCase} + +SUAS REGRAS DE CO-AUTORIA: +1. Aplique as diretrizes do modelo alvo em cada sugestao — nunca as ignore +2. Estruture o prompt nas secoes relevantes: Role, Context, Instruction, Format, Examples, Constraints +3. Marque cada secao com o prefixo // SECAO: NOME para que o PromptPreview possa identifica-la +4. Explique brevemente cada decisao estrutural (maximo 1 linha por decisao, prefixo //) +5. Sugira a proxima secao necessaria antes de o usuario pedir +6. Nunca gere prompts genericos — sempre especificos ao contexto informado +7. Use variaveis no formato {{nome_variavel}} para partes que devem ser dinamicas +8. Se o prompt requer exemplos few-shot, inclua pelo menos 2 exemplos concretos +9. Se detectar inconsistencia com as melhores praticas do modelo, sinalize e proponha alternativa +10. Ao finalizar uma secao, indique qual secao deve vir a seguir e por que` +} diff --git a/promptforge/src/bestPractices/google.ts b/promptforge/src/bestPractices/google.ts new file mode 100644 index 0000000..e881fa3 --- /dev/null +++ b/promptforge/src/bestPractices/google.ts @@ -0,0 +1,36 @@ +import { PromptGuidelines } from '../types/guidelines' + +export const gemini3Guidelines: PromptGuidelines = { + preferredStructure: 'declarative', + systemPromptStyle: 'context-rich-declarative', + reasoningControl: 'use-thinking-level-parameter-not-budget', + boostPatterns: [ + 'grounding-with-google-search-quando-necessario', + 'structured-output-json-mode', + 'leverage-1M-context-window', + 'declarative-natural-language-instructions', + ], + avoidPatterns: [ + 'temperature-below-1.0', + 'thinking-budget-usar-thinking-level', + 'thought-signature-bypass', + ], + maxTemperature: 2.0, + temperatureNote: + 'CRITICO: Nao definir temperature abaixo de 1.0 para Gemini 3.x. Causa loops ou degradacao.', + chainOfThoughtNote: 'Usar thinking_level (minimal/low/medium/high), nao thinking_budget.', +} + +export const gemini25Guidelines: PromptGuidelines = { + preferredStructure: 'declarative', + systemPromptStyle: 'context-rich-declarative', + reasoningControl: 'use-thinking-budget-integer-tokens', + boostPatterns: [ + 'declarative-natural-language-instructions', + 'structured-output-json-mode', + 'explicit-output-format', + ], + avoidPatterns: ['thought-signature-bypass'], + chainOfThoughtNote: + 'Gemini 2.5 usa thinking_budget (inteiro de tokens), diferente do thinking_level do Gemini 3.', +} diff --git a/promptforge/src/bestPractices/index.ts b/promptforge/src/bestPractices/index.ts new file mode 100644 index 0000000..e077e84 --- /dev/null +++ b/promptforge/src/bestPractices/index.ts @@ -0,0 +1,13 @@ +import { PromptGuidelines } from '../types/guidelines' +import { claudeGuidelines } from './claude' +import { openaiGPT5Guidelines, openaiOSeriesGuidelines } from './openai' +import { gemini3Guidelines, gemini25Guidelines } from './google' + +export function getGuidelines(modelId: string): PromptGuidelines { + if (modelId.startsWith('claude')) return claudeGuidelines + if (modelId === 'o3' || modelId === 'o4-mini') return openaiOSeriesGuidelines + if (modelId.startsWith('gpt')) return openaiGPT5Guidelines + if (modelId.startsWith('gemini-3')) return gemini3Guidelines + if (modelId.startsWith('gemini-2')) return gemini25Guidelines + return openaiGPT5Guidelines // fallback seguro +} diff --git a/promptforge/src/bestPractices/openai.ts b/promptforge/src/bestPractices/openai.ts new file mode 100644 index 0000000..ebb61f8 --- /dev/null +++ b/promptforge/src/bestPractices/openai.ts @@ -0,0 +1,36 @@ +import { PromptGuidelines } from '../types/guidelines' + +export const openaiGPT5Guidelines: PromptGuidelines = { + preferredStructure: 'markdown-headers', + systemPromptStyle: 'concise-directive', + reasoningControl: 'native-reasoning-built-in', + boostPatterns: [ + 'clear-role-in-system-prompt', + 'few-shot-examples-inline', + 'explicit-output-format-with-example', + 'markdown-headers-for-sections', + ], + avoidPatterns: ['explicit-chain-of-thought-instructions', 'over-structured-xml'], + chainOfThoughtNote: + 'GPT-5 series tem raciocinio nativo. NAO instrua pensamento passo a passo.', +} + +export const openaiOSeriesGuidelines: PromptGuidelines = { + preferredStructure: 'plain-instruction', + systemPromptStyle: 'minimal-objective-focused', + reasoningControl: 'none-model-handles-internally', + boostPatterns: [ + 'clear-objective-statement', + 'explicit-success-criteria', + 'defined-constraints', + ], + avoidPatterns: [ + 'step-by-step-instructions', + 'verbose-reasoning-cues', + 'temperature-adjustment', + 'chain-of-thought-prompts', + ], + maxTemperature: 1.0, + chainOfThoughtNote: + 'NUNCA instrua o-series a pensar passo a passo. Foque no objetivo e restricoes.', +} diff --git a/promptforge/src/bestPractices/types.ts b/promptforge/src/bestPractices/types.ts new file mode 100644 index 0000000..5bf8373 --- /dev/null +++ b/promptforge/src/bestPractices/types.ts @@ -0,0 +1 @@ +export type { PromptGuidelines } from '../types/guidelines' diff --git a/promptforge/src/commands/configureKeys.ts b/promptforge/src/commands/configureKeys.ts new file mode 100644 index 0000000..d5abd24 --- /dev/null +++ b/promptforge/src/commands/configureKeys.ts @@ -0,0 +1,96 @@ +import * as vscode from 'vscode' + +const SECRET_KEYS = { + anthropic: 'promptforge.key.anthropic', + openai: 'promptforge.key.openai', + google: 'promptforge.key.google', + openrouter: 'promptforge.key.openrouter', +} as const + +let extensionContext: vscode.ExtensionContext | undefined + +export function setExtensionContext(context: vscode.ExtensionContext): void { + extensionContext = context +} + +function getContext(): vscode.ExtensionContext { + if (!extensionContext) { + throw new Error('Contexto da extensao nao inicializado') + } + return extensionContext +} + +export async function configureKeysCommand(): Promise { + const provider = await vscode.window.showQuickPick( + [ + { label: 'Anthropic (Claude)', value: 'anthropic' as const }, + { label: 'OpenAI (GPT / o-series)', value: 'openai' as const }, + { label: 'Google (Gemini)', value: 'google' as const }, + { label: 'OpenRouter', value: 'openrouter' as const }, + ], + { placeHolder: 'Selecione o provider para configurar a API key' }, + ) + + if (!provider) return + + const key = await vscode.window.showInputBox({ + prompt: `Insira sua API key do ${provider.label}`, + password: true, + placeHolder: 'sk-...', + ignoreFocusOut: true, + }) + + if (!key) return + + const context = getContext() + await context.secrets.store(SECRET_KEYS[provider.value], key) + vscode.window.showInformationMessage(`API key do ${provider.label} salva com sucesso.`) +} + +export async function getApiKey(modelId: string): Promise { + const context = getContext() + + let secretKey: string + if (modelId.startsWith('claude')) { + secretKey = SECRET_KEYS.anthropic + } else if ( + modelId === 'o3' || + modelId === 'o4-mini' || + modelId.startsWith('gpt') + ) { + secretKey = SECRET_KEYS.openai + } else if (modelId.startsWith('gemini')) { + secretKey = SECRET_KEYS.google + } else if (modelId.startsWith('openrouter')) { + secretKey = SECRET_KEYS.openrouter + } else { + return undefined + } + + const stored = await context.secrets.get(secretKey) + if (stored) return stored + + // Fallback: variavel de ambiente em desenvolvimento + if (modelId.startsWith('claude')) return process.env.ANTHROPIC_API_KEY + if (modelId === 'o3' || modelId === 'o4-mini' || modelId.startsWith('gpt')) + return process.env.OPENAI_API_KEY + if (modelId.startsWith('gemini')) return process.env.GEMINI_API_KEY + if (modelId.startsWith('openrouter')) return process.env.OPENROUTER_API_KEY + + return undefined +} + +export async function getKeysStatus(): Promise<{ + anthropic: boolean + openai: boolean + google: boolean + openrouter: boolean +}> { + const context = getContext() + return { + anthropic: !!(await context.secrets.get(SECRET_KEYS.anthropic)) || !!process.env.ANTHROPIC_API_KEY, + openai: !!(await context.secrets.get(SECRET_KEYS.openai)) || !!process.env.OPENAI_API_KEY, + google: !!(await context.secrets.get(SECRET_KEYS.google)) || !!process.env.GEMINI_API_KEY, + openrouter: !!(await context.secrets.get(SECRET_KEYS.openrouter)) || !!process.env.OPENROUTER_API_KEY, + } +} diff --git a/promptforge/src/commands/newPrompt.ts b/promptforge/src/commands/newPrompt.ts new file mode 100644 index 0000000..c625e80 --- /dev/null +++ b/promptforge/src/commands/newPrompt.ts @@ -0,0 +1,6 @@ +import * as vscode from 'vscode' +import { EditorPanel } from '../panels/EditorPanel' + +export function newPromptCommand(extensionUri: vscode.Uri): void { + EditorPanel.createOrShow(extensionUri) +} diff --git a/promptforge/src/commands/openLibrary.ts b/promptforge/src/commands/openLibrary.ts new file mode 100644 index 0000000..f6c240c --- /dev/null +++ b/promptforge/src/commands/openLibrary.ts @@ -0,0 +1,6 @@ +import * as vscode from 'vscode' +import { LibraryPanel } from '../panels/LibraryPanel' + +export function openLibraryCommand(extensionUri: vscode.Uri): void { + LibraryPanel.createOrShow(extensionUri) +} diff --git a/promptforge/src/commands/testPrompt.ts b/promptforge/src/commands/testPrompt.ts new file mode 100644 index 0000000..0f3a4cc --- /dev/null +++ b/promptforge/src/commands/testPrompt.ts @@ -0,0 +1,6 @@ +import * as vscode from 'vscode' +import { TestPanel } from '../panels/TestPanel' + +export function testPromptCommand(extensionUri: vscode.Uri): void { + TestPanel.createOrShow(extensionUri) +} diff --git a/promptforge/src/evaluators/SemanticScorer.ts b/promptforge/src/evaluators/SemanticScorer.ts new file mode 100644 index 0000000..02e7914 --- /dev/null +++ b/promptforge/src/evaluators/SemanticScorer.ts @@ -0,0 +1,17 @@ +// Estrutura reservada para P2 (F3) — Avaliadores automaticos +// No MVP, o score semantico e calculado por sobreposicao de tokens no TestPanel + +export class SemanticScorer { + static score(actual: string, expected: string): number { + const normalize = (s: string) => + s + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 2) + const actualWords = new Set(normalize(actual)) + const expectedWords = normalize(expected) + if (expectedWords.length === 0) return 0 + const overlap = expectedWords.filter((w) => actualWords.has(w)).length + return Math.min(1, overlap / expectedWords.length) + } +} diff --git a/promptforge/src/extension.ts b/promptforge/src/extension.ts new file mode 100644 index 0000000..a3f4140 --- /dev/null +++ b/promptforge/src/extension.ts @@ -0,0 +1,47 @@ +import * as vscode from 'vscode' +import { EditorPanel } from './panels/EditorPanel' +import { TestPanel } from './panels/TestPanel' +import { LibraryPanel } from './panels/LibraryPanel' +import { setExtensionContext } from './commands/configureKeys' +import { PromptStore } from './storage/PromptStore' +import { ReplayStore } from './storage/ReplayStore' + +export function activate(context: vscode.ExtensionContext): void { + try { + setExtensionContext(context) + PromptStore.initialize(context) + ReplayStore.initialize(context) + } catch (error) { + vscode.window.showErrorMessage(`PromptForge: falha na ativacao — ${error}`) + return + } + + context.subscriptions.push( + vscode.commands.registerCommand('promptforge.newPrompt', () => { + EditorPanel.createOrShow(context.extensionUri) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand('promptforge.testPrompt', () => { + TestPanel.createOrShow(context.extensionUri) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand('promptforge.openLibrary', () => { + LibraryPanel.createOrShow(context.extensionUri) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand('promptforge.configureKeys', async () => { + const { configureKeysCommand } = await import('./commands/configureKeys') + await configureKeysCommand() + }), + ) +} + +export function deactivate(): void { + // Limpeza futura se necessario +} diff --git a/promptforge/src/panels/BasePanel.ts b/promptforge/src/panels/BasePanel.ts new file mode 100644 index 0000000..7901c99 --- /dev/null +++ b/promptforge/src/panels/BasePanel.ts @@ -0,0 +1,66 @@ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import { ExtensionMessage, WebviewMessage } from '../types/messages' + +export abstract class BasePanel { + protected readonly panel: vscode.WebviewPanel + protected readonly extensionUri: vscode.Uri + private readonly disposables: vscode.Disposable[] = [] + + constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + this.panel = panel + this.extensionUri = extensionUri + this.panel.onDidDispose(() => this.dispose(), null, this.disposables) + } + + protected getNonce(): string { + return crypto.randomBytes(16).toString('base64') + } + + protected getHtmlForWebview(webview: vscode.Webview): string { + const nonce = this.getNonce() + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview', 'main.js'), + ) + const styleUri = webview.asWebviewUri( + vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview', 'main.css'), + ) + const csp = [ + `default-src 'none'`, + `style-src ${webview.cspSource} 'unsafe-inline'`, + `script-src 'nonce-${nonce}'`, + `font-src ${webview.cspSource}`, + `img-src ${webview.cspSource} data:`, + ].join('; ') + + return ` + + + + + + + PromptForge + + +
+ + +` + } + + protected postMessage(message: ExtensionMessage): void { + this.panel.webview.postMessage(message) + } + + protected onWebviewMessage(handler: (msg: WebviewMessage) => void): void { + this.panel.webview.onDidReceiveMessage(handler, null, this.disposables) + } + + public dispose(): void { + while (this.disposables.length) { + this.disposables.pop()?.dispose() + } + this.panel.dispose() + } +} diff --git a/promptforge/src/panels/EditorPanel.ts b/promptforge/src/panels/EditorPanel.ts new file mode 100644 index 0000000..03d1cf3 --- /dev/null +++ b/promptforge/src/panels/EditorPanel.ts @@ -0,0 +1,138 @@ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import { BasePanel } from './BasePanel' +import { WebviewMessage } from '../types/messages' + +export class EditorPanel extends BasePanel { + public static currentPanel: EditorPanel | undefined + private static readonly viewType = 'promptforge.editor' + + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + super(panel, extensionUri) + this.panel.webview.html = this.getHtmlForWebview(this.panel.webview) + this.setupMessageListener() + } + + public static createOrShow(extensionUri: vscode.Uri): EditorPanel { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined + + if (EditorPanel.currentPanel) { + EditorPanel.currentPanel.panel.reveal(column) + return EditorPanel.currentPanel + } + + const panel = vscode.window.createWebviewPanel( + EditorPanel.viewType, + 'PromptForge - Editor', + column || vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + }, + ) + + EditorPanel.currentPanel = new EditorPanel(panel, extensionUri) + return EditorPanel.currentPanel + } + + private setupMessageListener(): void { + this.onWebviewMessage((msg: WebviewMessage) => { + this.handleMessage(msg) + }) + } + + private async handleMessage(msg: WebviewMessage): Promise { + switch (msg.type) { + case 'GENERATE_PROMPT': + await this.handleGeneratePrompt(msg.payload) + break + case 'SAVE_PROMPT': + await this.handleSavePrompt(msg.payload) + break + case 'CONFIGURE_KEYS': + vscode.commands.executeCommand('promptforge.configureKeys') + break + } + } + + private async handleGeneratePrompt( + payload: WebviewMessage extends { type: 'GENERATE_PROMPT'; payload: infer P } ? P : never, + ): Promise { + // Implementado na Sessao 3 + const { getGuidelines } = await import('../bestPractices/index') + const { buildCowriterSystemPrompt } = await import( + '../bestPractices/cowriterSystemPrompt' + ) + const { ProviderFactory } = await import('../providers/ModelProvider') + const { streamToWebview } = await import('../utils/streamBridge') + const { calculateCost } = await import('../utils/tokenCounter') + const { getApiKey } = await import('../commands/configureKeys') + + try { + const guidelines = getGuidelines(payload.targetModel) + const systemPrompt = buildCowriterSystemPrompt( + payload.targetModel, + guidelines, + payload.userIntent, + payload.userContext, + payload.audience, + payload.useCase, + ) + + const apiKey = await getApiKey(payload.targetModel) + if (!apiKey) { + this.postMessage({ + type: 'ERROR', + payload: { message: 'API key nao configurada para este modelo', code: 'NO_API_KEY' }, + }) + return + } + + const provider = ProviderFactory.create(payload.targetModel, apiKey) + const userMessage = payload.currentDraft + ? `Prompt atual do usuario:\n${payload.currentDraft}\n\nContinue co-escrevendo e sugira melhorias.` + : `Crie um prompt completo baseado nas informacoes fornecidas no contexto.` + + const streamId = crypto.randomUUID() + const generator = provider.stream(systemPrompt, userMessage, payload.params) + const inputTokens = Math.ceil((systemPrompt.length + userMessage.length) / 4) + + await streamToWebview( + generator, + this.panel, + streamId, + (input: number, output: number) => calculateCost(payload.targetModel, input, output), + inputTokens, + ) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'GENERATE_ERROR' }, + }) + } + } + + private async handleSavePrompt( + payload: WebviewMessage extends { type: 'SAVE_PROMPT'; payload: infer P } ? P : never, + ): Promise { + const { PromptStore } = await import('../storage/PromptStore') + try { + const store = PromptStore.getInstance() + const promptId = await store.savePrompt(payload) + this.postMessage({ type: 'PROMPT_SAVED', payload: { promptId } }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'SAVE_ERROR' }, + }) + } + } + + public override dispose(): void { + EditorPanel.currentPanel = undefined + super.dispose() + } +} diff --git a/promptforge/src/panels/LibraryPanel.ts b/promptforge/src/panels/LibraryPanel.ts new file mode 100644 index 0000000..669b229 --- /dev/null +++ b/promptforge/src/panels/LibraryPanel.ts @@ -0,0 +1,133 @@ +import * as vscode from 'vscode' +import { BasePanel } from './BasePanel' +import { WebviewMessage } from '../types/messages' + +export class LibraryPanel extends BasePanel { + public static currentPanel: LibraryPanel | undefined + private static readonly viewType = 'promptforge.library' + + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + super(panel, extensionUri) + this.panel.webview.html = this.getHtmlForWebview(this.panel.webview) + this.setupMessageListener() + } + + public static createOrShow(extensionUri: vscode.Uri): LibraryPanel { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined + + if (LibraryPanel.currentPanel) { + LibraryPanel.currentPanel.panel.reveal(column) + return LibraryPanel.currentPanel + } + + const panel = vscode.window.createWebviewPanel( + LibraryPanel.viewType, + 'PromptForge - Biblioteca', + column || vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + }, + ) + + LibraryPanel.currentPanel = new LibraryPanel(panel, extensionUri) + return LibraryPanel.currentPanel + } + + private setupMessageListener(): void { + this.onWebviewMessage((msg: WebviewMessage) => { + this.handleMessage(msg) + }) + } + + private async handleMessage(msg: WebviewMessage): Promise { + switch (msg.type) { + case 'LOAD_LIBRARY': + await this.handleLoadLibrary() + break + case 'SAVE_PROMPT': + await this.handleSavePrompt(msg.payload) + break + case 'DELETE_PROMPT': + await this.handleDeletePrompt(msg.payload) + break + case 'GENERATE_SNIPPET': + await this.handleGenerateSnippet(msg.payload) + break + case 'CONFIGURE_KEYS': + vscode.commands.executeCommand('promptforge.configureKeys') + break + } + } + + private async handleLoadLibrary(): Promise { + const { PromptStore } = await import('../storage/PromptStore') + try { + const store = PromptStore.getInstance() + const prompts = await store.listPrompts() + this.postMessage({ type: 'LIBRARY_DATA', payload: { prompts } }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'LIBRARY_LOAD_ERROR' }, + }) + } + } + + private async handleSavePrompt( + payload: WebviewMessage extends { type: 'SAVE_PROMPT'; payload: infer P } ? P : never, + ): Promise { + const { PromptStore } = await import('../storage/PromptStore') + try { + const store = PromptStore.getInstance() + const promptId = await store.savePrompt(payload) + this.postMessage({ type: 'PROMPT_SAVED', payload: { promptId } }) + await this.handleLoadLibrary() + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'SAVE_ERROR' }, + }) + } + } + + private async handleDeletePrompt(payload: { promptId: string }): Promise { + const { PromptStore } = await import('../storage/PromptStore') + try { + const store = PromptStore.getInstance() + await store.deletePrompt(payload.promptId) + await this.handleLoadLibrary() + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'DELETE_ERROR' }, + }) + } + } + + private async handleGenerateSnippet( + payload: WebviewMessage extends { type: 'GENERATE_SNIPPET'; payload: infer P } ? P : never, + ): Promise { + const { generateSnippet } = await import('../utils/snippetGenerator') + try { + const code = generateSnippet(payload) + this.postMessage({ + type: 'SNIPPET_RESULT', + payload: { code, language: payload.language }, + }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'SNIPPET_ERROR' }, + }) + } + } + + public override dispose(): void { + LibraryPanel.currentPanel = undefined + super.dispose() + } +} diff --git a/promptforge/src/panels/TestPanel.ts b/promptforge/src/panels/TestPanel.ts new file mode 100644 index 0000000..bddac70 --- /dev/null +++ b/promptforge/src/panels/TestPanel.ts @@ -0,0 +1,322 @@ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import { BasePanel } from './BasePanel' +import { WebviewMessage, TestResultPayload } from '../types/messages' +import { CostInfo } from '../types/models' + +export class TestPanel extends BasePanel { + public static currentPanel: TestPanel | undefined + private static readonly viewType = 'promptforge.test' + + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + super(panel, extensionUri) + this.panel.webview.html = this.getHtmlForWebview(this.panel.webview) + this.setupMessageListener() + } + + public static createOrShow(extensionUri: vscode.Uri): TestPanel { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined + + if (TestPanel.currentPanel) { + TestPanel.currentPanel.panel.reveal(column) + return TestPanel.currentPanel + } + + const panel = vscode.window.createWebviewPanel( + TestPanel.viewType, + 'PromptForge - Teste', + column || vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + }, + ) + + TestPanel.currentPanel = new TestPanel(panel, extensionUri) + return TestPanel.currentPanel + } + + private setupMessageListener(): void { + this.onWebviewMessage((msg: WebviewMessage) => { + this.handleMessage(msg) + }) + } + + private async handleMessage(msg: WebviewMessage): Promise { + switch (msg.type) { + case 'RUN_TEST': + await this.handleRunTest(msg.payload) + break + case 'RUN_AB_TEST': + await this.handleABTest(msg.payload) + break + case 'REFINE_PROMPT': + await this.handleRefine(msg.payload) + break + case 'CAPTURE_REPLAY': + await this.handleCaptureReplay(msg.payload) + break + case 'LOAD_REPLAYS': + await this.handleLoadReplays() + break + case 'CONFIGURE_KEYS': + vscode.commands.executeCommand('promptforge.configureKeys') + break + } + } + + private async handleRunTest( + payload: WebviewMessage extends { type: 'RUN_TEST'; payload: infer P } ? P : never, + ): Promise { + const { ProviderFactory } = await import('../providers/ModelProvider') + const { calculateCost } = await import('../utils/tokenCounter') + const { getApiKey } = await import('../commands/configureKeys') + const { resolveVariables } = await import('../utils/variableParser') + + try { + const apiKey = await getApiKey(payload.model) + if (!apiKey) { + this.postMessage({ + type: 'ERROR', + payload: { message: 'API key nao configurada', code: 'NO_API_KEY' }, + }) + return + } + + const provider = ProviderFactory.create(payload.model, apiKey) + const resolvedPrompt = resolveVariables(payload.userPromptTemplate, payload.variables) + const userMessage = payload.userInput + ? `${resolvedPrompt}\n\n${payload.userInput}` + : resolvedPrompt + + const start = Date.now() + const result = await provider.complete(payload.systemPrompt, userMessage, payload.params) + const latencyMs = Date.now() - start + + const cost = calculateCost(payload.model, result.inputTokens, result.outputTokens) + + let semanticScore: number | undefined + if (payload.desiredOutput) { + semanticScore = this.calculateSemanticScore(result.text, payload.desiredOutput) + } + + const testResult: TestResultPayload = { + runId: crypto.randomUUID(), + output: result.text, + cost, + latencyMs, + semanticScore, + } + + this.postMessage({ type: 'TEST_RESULT', payload: testResult }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'TEST_ERROR' }, + }) + } + } + + private calculateSemanticScore(actual: string, desired: string): number { + const normalize = (s: string) => + s + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 2) + const actualWords = new Set(normalize(actual)) + const desiredWords = normalize(desired) + if (desiredWords.length === 0) return 0 + const overlap = desiredWords.filter((w) => actualWords.has(w)).length + return Math.min(1, overlap / desiredWords.length) + } + + private async handleABTest( + payload: WebviewMessage extends { type: 'RUN_AB_TEST'; payload: infer P } ? P : never, + ): Promise { + const { ProviderFactory } = await import('../providers/ModelProvider') + const { calculateCost } = await import('../utils/tokenCounter') + const { getApiKey } = await import('../commands/configureKeys') + const { resolveVariables } = await import('../utils/variableParser') + + try { + const apiKey = await getApiKey(payload.model) + if (!apiKey) { + this.postMessage({ + type: 'ERROR', + payload: { message: 'API key nao configurada', code: 'NO_API_KEY' }, + }) + return + } + + const provider = ProviderFactory.create(payload.model, apiKey) + const resolvedA = resolveVariables(payload.promptA.userPromptTemplate, payload.variables) + const resolvedB = resolveVariables(payload.promptB.userPromptTemplate, payload.variables) + + const resultsA: TestResultPayload[] = [] + const resultsB: TestResultPayload[] = [] + + const runPair = async (input: string) => { + const [rA, rB] = await Promise.all([ + (async () => { + const start = Date.now() + const res = await provider.complete( + payload.promptA.systemPrompt, + `${resolvedA}\n\n${input}`, + payload.params, + ) + return { + result: res, + latencyMs: Date.now() - start, + } + })(), + (async () => { + const start = Date.now() + const res = await provider.complete( + payload.promptB.systemPrompt, + `${resolvedB}\n\n${input}`, + payload.params, + ) + return { + result: res, + latencyMs: Date.now() - start, + } + })(), + ]) + + resultsA.push({ + runId: crypto.randomUUID(), + output: rA.result.text, + cost: calculateCost(payload.model, rA.result.inputTokens, rA.result.outputTokens), + latencyMs: rA.latencyMs, + }) + + resultsB.push({ + runId: crypto.randomUUID(), + output: rB.result.text, + cost: calculateCost(payload.model, rB.result.inputTokens, rB.result.outputTokens), + latencyMs: rB.latencyMs, + }) + } + + await Promise.all(payload.inputs.map(runPair)) + + const avg = (arr: TestResultPayload[], key: 'latencyMs') => + arr.reduce((sum, r) => sum + r[key], 0) / arr.length + const avgCost = (arr: TestResultPayload[]) => + arr.reduce((sum, r) => sum + r.cost.totalCostUSD, 0) / arr.length + + this.postMessage({ + type: 'AB_RESULT', + payload: { + inputs: payload.inputs, + resultsA, + resultsB, + summaryA: { avgCostUSD: avgCost(resultsA), avgLatencyMs: avg(resultsA, 'latencyMs') }, + summaryB: { avgCostUSD: avgCost(resultsB), avgLatencyMs: avg(resultsB, 'latencyMs') }, + }, + }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'AB_TEST_ERROR' }, + }) + } + } + + private async handleRefine( + payload: WebviewMessage extends { type: 'REFINE_PROMPT'; payload: infer P } ? P : never, + ): Promise { + const { ProviderFactory } = await import('../providers/ModelProvider') + const { streamToWebview } = await import('../utils/streamBridge') + const { calculateCost } = await import('../utils/tokenCounter') + const { getApiKey } = await import('../commands/configureKeys') + + try { + const apiKey = await getApiKey(payload.model) + if (!apiKey) { + this.postMessage({ + type: 'ERROR', + payload: { message: 'API key nao configurada', code: 'NO_API_KEY' }, + }) + return + } + + const provider = ProviderFactory.create(payload.model, apiKey) + const systemPrompt = `Voce e um especialista em refinamento de prompts. +Analise o gap entre o output real e o output desejado. +Aplique estas diretrizes do modelo: ${payload.guidelinesText} +Reescreva o prompt para eliminar o gap identificado.` + + const userMessage = `PROMPT ORIGINAL (system): +${payload.systemPrompt} + +PROMPT ORIGINAL (user template): +${payload.userPromptTemplate} + +INPUT DE TESTE: +${payload.lastInput} + +OUTPUT REAL: +${payload.actualOutput} + +OUTPUT DESEJADO: +${payload.desiredOutput} + +Reescreva o system prompt e o user prompt template para que o output se aproxime do desejado.` + + const streamId = crypto.randomUUID() + const generator = provider.stream(systemPrompt, userMessage, payload.params) + const inputTokens = Math.ceil((systemPrompt.length + userMessage.length) / 4) + + await streamToWebview( + generator, + this.panel, + streamId, + (input: number, output: number) => calculateCost(payload.model, input, output), + inputTokens, + ) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'REFINE_ERROR' }, + }) + } + } + + private async handleCaptureReplay( + payload: WebviewMessage extends { type: 'CAPTURE_REPLAY'; payload: infer P } ? P : never, + ): Promise { + const { ReplayStore } = await import('../storage/ReplayStore') + try { + const store = ReplayStore.getInstance() + await store.captureReplay(payload) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'REPLAY_CAPTURE_ERROR' }, + }) + } + } + + private async handleLoadReplays(): Promise { + const { ReplayStore } = await import('../storage/ReplayStore') + try { + const store = ReplayStore.getInstance() + const replays = await store.loadReplays() + this.postMessage({ type: 'REPLAYS_DATA', payload: { replays } }) + } catch (error) { + this.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'REPLAY_LOAD_ERROR' }, + }) + } + } + + public override dispose(): void { + TestPanel.currentPanel = undefined + super.dispose() + } +} diff --git a/promptforge/src/providers/AnthropicProvider.ts b/promptforge/src/providers/AnthropicProvider.ts new file mode 100644 index 0000000..9ca2d3b --- /dev/null +++ b/promptforge/src/providers/AnthropicProvider.ts @@ -0,0 +1,73 @@ +import Anthropic from '@anthropic-ai/sdk' +import { ModelProvider } from './ModelProvider' +import { CompletionResult, ModelParams, CostInfo, DEFAULT_PARAMS } from '../types/models' +import { PromptGuidelines } from '../types/guidelines' +import { claudeGuidelines } from '../bestPractices/claude' +import { calculateCost } from '../utils/tokenCounter' + +export class AnthropicProvider implements ModelProvider { + private client: Anthropic + private modelId: string + + constructor(modelId: string, apiKey: string) { + this.modelId = modelId + this.client = new Anthropic({ apiKey }) + } + + async complete(system: string, user: string, params: ModelParams): Promise { + const start = Date.now() + const response = await this.client.messages.create({ + model: params.model || this.modelId, + max_tokens: params.maxTokens, + system, + messages: [{ role: 'user', content: user }], + }) + + const firstBlock = response.content?.[0] + const text = firstBlock && firstBlock.type === 'text' ? firstBlock.text : '' + + return { + text, + inputTokens: response.usage.input_tokens, + outputTokens: response.usage.output_tokens, + model: this.modelId, + latencyMs: Date.now() - start, + } + } + + async *stream(system: string, user: string, params: ModelParams): AsyncGenerator { + const stream = this.client.messages.stream({ + model: params.model || this.modelId, + max_tokens: params.maxTokens, + system, + messages: [{ role: 'user', content: user }], + }) + + for await (const event of stream) { + if ( + event.type === 'content_block_delta' && + event.delta.type === 'text_delta' + ) { + yield event.delta.text + } + } + } + + estimateCost(inputTokens: number, outputTokens: number): CostInfo { + return calculateCost(this.modelId, inputTokens, outputTokens) + } + + getBestPractices(): PromptGuidelines { + return claudeGuidelines + } + + getDefaultParams(): ModelParams { + const defaults = DEFAULT_PARAMS[this.modelId] || {} + return { + model: this.modelId, + temperature: defaults.temperature ?? 1.0, + maxTokens: defaults.maxTokens ?? 4096, + ...defaults, + } + } +} diff --git a/promptforge/src/providers/GoogleProvider.ts b/promptforge/src/providers/GoogleProvider.ts new file mode 100644 index 0000000..73cb99c --- /dev/null +++ b/promptforge/src/providers/GoogleProvider.ts @@ -0,0 +1,69 @@ +import { GoogleGenerativeAI } from '@google/generative-ai' +import { ModelProvider } from './ModelProvider' +import { CompletionResult, ModelParams, CostInfo, DEFAULT_PARAMS } from '../types/models' +import { PromptGuidelines } from '../types/guidelines' +import { gemini3Guidelines, gemini25Guidelines } from '../bestPractices/google' +import { calculateCost } from '../utils/tokenCounter' + +export class GoogleProvider implements ModelProvider { + private genAI: GoogleGenerativeAI + private modelId: string + + constructor(modelId: string, apiKey: string) { + this.modelId = modelId + this.genAI = new GoogleGenerativeAI(apiKey) + } + + async complete(system: string, user: string, params: ModelParams): Promise { + const start = Date.now() + const model = this.genAI.getGenerativeModel({ + model: params.model || this.modelId, + systemInstruction: system, + }) + + const result = await model.generateContent(user) + const text = result.response.text() + + return { + text, + inputTokens: Math.ceil((system.length + user.length) / 4), + outputTokens: Math.ceil(text.length / 4), + model: this.modelId, + latencyMs: Date.now() - start, + } + } + + async *stream(system: string, user: string, params: ModelParams): AsyncGenerator { + const model = this.genAI.getGenerativeModel({ + model: params.model || this.modelId, + systemInstruction: system, + }) + + const result = await model.generateContentStream(user) + + for await (const chunk of result.stream) { + const text = chunk.text() + if (text) { + yield text + } + } + } + + estimateCost(inputTokens: number, outputTokens: number): CostInfo { + return calculateCost(this.modelId, inputTokens, outputTokens) + } + + getBestPractices(): PromptGuidelines { + return this.modelId.startsWith('gemini-3') ? gemini3Guidelines : gemini25Guidelines + } + + getDefaultParams(): ModelParams { + const defaults = DEFAULT_PARAMS[this.modelId] || {} + return { + model: this.modelId, + temperature: defaults.temperature ?? 1.0, + maxTokens: defaults.maxTokens ?? 4096, + ...defaults, + } + } +} diff --git a/promptforge/src/providers/ModelProvider.ts b/promptforge/src/providers/ModelProvider.ts new file mode 100644 index 0000000..7ab6029 --- /dev/null +++ b/promptforge/src/providers/ModelProvider.ts @@ -0,0 +1,33 @@ +import { CompletionResult, ModelParams, CostInfo } from '../types/models' +import { PromptGuidelines } from '../types/guidelines' + +export interface ModelProvider { + complete(system: string, user: string, params: ModelParams): Promise + stream(system: string, user: string, params: ModelParams): AsyncGenerator + estimateCost(inputTokens: number, outputTokens: number): CostInfo + getBestPractices(): PromptGuidelines + getDefaultParams(): ModelParams +} + +export class ProviderFactory { + static create(modelId: string, apiKey: string): ModelProvider { + // Importacoes dinamicas para evitar carregar todos os SDKs na inicializacao + if (modelId.startsWith('claude')) { + const { AnthropicProvider } = require('./AnthropicProvider') + return new AnthropicProvider(modelId, apiKey) + } + if (modelId === 'o3' || modelId === 'o4-mini' || modelId.startsWith('gpt')) { + const { OpenAIProvider } = require('./OpenAIProvider') + return new OpenAIProvider(modelId, apiKey) + } + if (modelId.startsWith('gemini')) { + const { GoogleProvider } = require('./GoogleProvider') + return new GoogleProvider(modelId, apiKey) + } + if (modelId.startsWith('openrouter')) { + const { OpenRouterProvider } = require('./OpenRouterProvider') + return new OpenRouterProvider(modelId, apiKey) + } + throw new Error(`Modelo desconhecido: ${modelId}`) + } +} diff --git a/promptforge/src/providers/OpenAIProvider.ts b/promptforge/src/providers/OpenAIProvider.ts new file mode 100644 index 0000000..3aef2f3 --- /dev/null +++ b/promptforge/src/providers/OpenAIProvider.ts @@ -0,0 +1,109 @@ +import OpenAI from 'openai' +import { ModelProvider } from './ModelProvider' +import { CompletionResult, ModelParams, CostInfo, DEFAULT_PARAMS } from '../types/models' +import { PromptGuidelines } from '../types/guidelines' +import { openaiGPT5Guidelines, openaiOSeriesGuidelines } from '../bestPractices/openai' +import { calculateCost } from '../utils/tokenCounter' + +// Tipagem local para a Responses API do OpenAI SDK >= 6.x +// O SDK pode nao exportar estes tipos diretamente +interface ResponsesCreateParams { + model: string + max_output_tokens: number + instructions: string + input: string + temperature?: number + stream?: boolean +} + +interface ResponsesResult { + output_text: string + usage?: { input_tokens: number; output_tokens: number } +} + +interface ResponsesStreamEvent { + type: string + delta?: string +} + +interface OpenAIWithResponses extends OpenAI { + responses: { + create(params: ResponsesCreateParams): Promise + } +} + +export class OpenAIProvider implements ModelProvider { + private client: OpenAIWithResponses + private modelId: string + private isOSeries: boolean + + constructor(modelId: string, apiKey: string) { + this.modelId = modelId + // Cast unico no construtor — Responses API pode nao ter tipos no SDK + this.client = new OpenAI({ apiKey }) as unknown as OpenAIWithResponses + this.isOSeries = modelId === 'o3' || modelId === 'o4-mini' + } + + async complete(system: string, user: string, params: ModelParams): Promise { + const start = Date.now() + + const requestParams: ResponsesCreateParams = { + model: params.model || this.modelId, + max_output_tokens: params.maxTokens, + instructions: system, + input: user, + } + if (!this.isOSeries) { + requestParams.temperature = params.temperature + } + const response = await this.client.responses.create(requestParams) + + return { + text: response.output_text || '', + inputTokens: response.usage?.input_tokens || Math.ceil((system.length + user.length) / 4), + outputTokens: + response.usage?.output_tokens || Math.ceil((response.output_text || '').length / 4), + model: this.modelId, + latencyMs: Date.now() - start, + } + } + + async *stream(system: string, user: string, params: ModelParams): AsyncGenerator { + const requestParams: ResponsesCreateParams = { + model: params.model || this.modelId, + max_output_tokens: params.maxTokens, + instructions: system, + input: user, + stream: true, + } + if (!this.isOSeries) { + requestParams.temperature = params.temperature + } + // Stream retorna AsyncIterable — tipado como unknown e iterado com type guard + const stream = (await this.client.responses.create(requestParams)) as unknown as AsyncIterable + + for await (const event of stream) { + if (event.type === 'response.output_text.delta') { + yield event.delta || '' + } + } + } + + estimateCost(inputTokens: number, outputTokens: number): CostInfo { + return calculateCost(this.modelId, inputTokens, outputTokens) + } + + getBestPractices(): PromptGuidelines { + return this.isOSeries ? openaiOSeriesGuidelines : openaiGPT5Guidelines + } + + getDefaultParams(): ModelParams { + const defaults = DEFAULT_PARAMS[this.modelId] || {} + return { + model: this.modelId, + temperature: defaults.temperature ?? 1.0, + maxTokens: defaults.maxTokens ?? 4096, + ...defaults, + } + } +} diff --git a/promptforge/src/providers/OpenRouterProvider.ts b/promptforge/src/providers/OpenRouterProvider.ts new file mode 100644 index 0000000..d0c8922 --- /dev/null +++ b/promptforge/src/providers/OpenRouterProvider.ts @@ -0,0 +1,88 @@ +import OpenAI from 'openai' +import { ModelProvider } from './ModelProvider' +import { CompletionResult, ModelParams, CostInfo, DEFAULT_PARAMS } from '../types/models' +import { PromptGuidelines } from '../types/guidelines' +import { openaiGPT5Guidelines } from '../bestPractices/openai' +import { calculateCost } from '../utils/tokenCounter' + +export class OpenRouterProvider implements ModelProvider { + private client: OpenAI + private modelId: string + + constructor(modelId: string, apiKey: string) { + this.modelId = modelId + this.client = new OpenAI({ + apiKey, + baseURL: 'https://openrouter.ai/api/v1', + defaultHeaders: { + 'HTTP-Referer': 'https://promptforge.dev', + 'X-Title': 'PromptForge', + }, + }) + } + + async complete(system: string, user: string, params: ModelParams): Promise { + const start = Date.now() + const routerModel = this.modelId.replace('openrouter/', '') + + const response = await this.client.chat.completions.create({ + model: routerModel, + max_tokens: params.maxTokens, + temperature: params.temperature, + messages: [ + { role: 'system', content: system }, + { role: 'user', content: user }, + ], + }) + + const text = response.choices[0]?.message?.content || '' + + return { + text, + inputTokens: response.usage?.prompt_tokens || Math.ceil((system.length + user.length) / 4), + outputTokens: response.usage?.completion_tokens || Math.ceil(text.length / 4), + model: this.modelId, + latencyMs: Date.now() - start, + } + } + + async *stream(system: string, user: string, params: ModelParams): AsyncGenerator { + const routerModel = this.modelId.replace('openrouter/', '') + + const stream = await this.client.chat.completions.create({ + model: routerModel, + max_tokens: params.maxTokens, + temperature: params.temperature, + messages: [ + { role: 'system', content: system }, + { role: 'user', content: user }, + ], + stream: true, + }) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta?.content + if (delta) { + yield delta + } + } + } + + estimateCost(inputTokens: number, outputTokens: number): CostInfo { + return calculateCost(this.modelId, inputTokens, outputTokens) + } + + getBestPractices(): PromptGuidelines { + return openaiGPT5Guidelines + } + + getDefaultParams(): ModelParams { + const defaults = DEFAULT_PARAMS[this.modelId] || {} + return { + model: this.modelId, + temperature: defaults.temperature ?? 1.0, + maxTokens: defaults.maxTokens ?? 4096, + ...defaults, + } + } +} diff --git a/promptforge/src/storage/DatasetStore.ts b/promptforge/src/storage/DatasetStore.ts new file mode 100644 index 0000000..0e5ad24 --- /dev/null +++ b/promptforge/src/storage/DatasetStore.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode' +import * as fs from 'fs' +import * as path from 'path' + +// Estrutura reservada para P1 (F4) — Dataset Management +// No MVP, apenas a estrutura base existe sem UI + +export interface Dataset { + id: string + name: string + createdAt: string + rows: Record[] +} + +export class DatasetStore { + private static instance: DatasetStore + private context: vscode.ExtensionContext + + private constructor(context: vscode.ExtensionContext) { + this.context = context + } + + static initialize(context: vscode.ExtensionContext): void { + DatasetStore.instance = new DatasetStore(context) + } + + static getInstance(): DatasetStore { + if (!DatasetStore.instance) { + throw new Error('DatasetStore nao inicializado.') + } + return DatasetStore.instance + } + + private getDatasetsDir(): string { + const workspaceFolders = vscode.workspace.workspaceFolders + if (!workspaceFolders) { + throw new Error('Nenhum workspace aberto') + } + const dir = path.join(workspaceFolders[0].uri.fsPath, '.promptforge', 'datasets') + fs.mkdirSync(dir, { recursive: true }) + return dir + } +} diff --git a/promptforge/src/storage/PromptStore.ts b/promptforge/src/storage/PromptStore.ts new file mode 100644 index 0000000..bc152ab --- /dev/null +++ b/promptforge/src/storage/PromptStore.ts @@ -0,0 +1,171 @@ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import * as fs from 'fs' +import * as path from 'path' +import { SavedPrompt, PromptVersion, SCHEMA_VERSION } from '../types/prompts' +import { SavePromptPayload } from '../types/messages' + +export class PromptStore { + private static instance: PromptStore + private context: vscode.ExtensionContext + + private constructor(context: vscode.ExtensionContext) { + this.context = context + } + + static initialize(context: vscode.ExtensionContext): void { + PromptStore.instance = new PromptStore(context) + } + + static getInstance(): PromptStore { + if (!PromptStore.instance) { + throw new Error('PromptStore nao inicializado. Chame initialize() primeiro.') + } + return PromptStore.instance + } + + private getPromptsDir(): string { + const workspaceFolders = vscode.workspace.workspaceFolders + if (!workspaceFolders) { + throw new Error('Nenhum workspace aberto') + } + const dir = path.join(workspaceFolders[0].uri.fsPath, '.promptforge', 'prompts') + fs.mkdirSync(dir, { recursive: true }) + return dir + } + + private getArchiveDir(): string { + const workspaceFolders = vscode.workspace.workspaceFolders + if (!workspaceFolders) { + throw new Error('Nenhum workspace aberto') + } + const dir = path.join(workspaceFolders[0].uri.fsPath, '.promptforge', 'archive') + fs.mkdirSync(dir, { recursive: true }) + return dir + } + + async savePrompt(payload: SavePromptPayload): Promise { + const dir = this.getPromptsDir() + let prompt: SavedPrompt + + if (payload.promptId) { + const existing = await this.getPrompt(payload.promptId) + if (!existing) { + throw new Error(`Prompt nao encontrado: ${payload.promptId}`) + } + + const newVersion: PromptVersion = { + versionId: crypto.randomUUID(), + versionNumber: `v${existing.versions.length + 1}`, + createdAt: new Date().toISOString(), + model: payload.model, + params: payload.params, + systemPrompt: payload.systemPrompt, + userPromptTemplate: payload.userPromptTemplate, + variables: payload.variables, + status: 'draft', + testHistory: [], + avgCostUSD: 0, + totalRuns: 0, + } + + existing.versions.push(newVersion) + existing.updatedAt = new Date().toISOString() + existing.name = payload.name + existing.description = payload.description + existing.tags = payload.tags + + // Arquivar versoes excedentes (maximo 15) + if (existing.versions.length > 15) { + const toArchive = existing.versions.splice(0, existing.versions.length - 15) + await this.archiveVersions(existing.id, toArchive) + } + + prompt = existing + } else { + const id = crypto.randomUUID() + const now = new Date().toISOString() + prompt = { + schemaVersion: SCHEMA_VERSION, + id, + name: payload.name, + description: payload.description, + createdAt: now, + updatedAt: now, + versions: [ + { + versionId: crypto.randomUUID(), + versionNumber: 'v1', + createdAt: now, + model: payload.model, + params: payload.params, + systemPrompt: payload.systemPrompt, + userPromptTemplate: payload.userPromptTemplate, + variables: payload.variables, + status: 'draft', + testHistory: [], + avgCostUSD: 0, + totalRuns: 0, + }, + ], + tags: payload.tags, + } + } + + const filePath = path.join(dir, `${prompt.id}.json`) + await fs.promises.writeFile(filePath, JSON.stringify(prompt, null, 2), 'utf-8') + return prompt.id + } + + async getPrompt(promptId: string): Promise { + const dir = this.getPromptsDir() + const filePath = path.join(dir, `${promptId}.json`) + if (!fs.existsSync(filePath)) return null + const data = await fs.promises.readFile(filePath, 'utf-8') + try { + return JSON.parse(data) as SavedPrompt + } catch { + throw new Error(`Arquivo de prompt corrompido: ${filePath}`) + } + } + + async listPrompts(): Promise { + const dir = this.getPromptsDir() + if (!fs.existsSync(dir)) return [] + const files = (await fs.promises.readdir(dir)).filter((f) => f.endsWith('.json')) + const prompts: SavedPrompt[] = [] + for (const f of files) { + try { + const data = await fs.promises.readFile(path.join(dir, f), 'utf-8') + prompts.push(JSON.parse(data) as SavedPrompt) + } catch { + // Ignorar arquivos corrompidos na listagem + } + } + return prompts + } + + async deletePrompt(promptId: string): Promise { + const dir = this.getPromptsDir() + const filePath = path.join(dir, `${promptId}.json`) + if (fs.existsSync(filePath)) { + await fs.promises.unlink(filePath) + } + } + + private async archiveVersions(promptId: string, versions: PromptVersion[]): Promise { + const dir = this.getArchiveDir() + const filePath = path.join(dir, `${promptId}-archived.json`) + let existing: PromptVersion[] = [] + if (fs.existsSync(filePath)) { + try { + const data = await fs.promises.readFile(filePath, 'utf-8') + existing = JSON.parse(data) + } catch { + existing = [] + } + } + existing.push(...versions) + await fs.promises.writeFile(filePath, JSON.stringify(existing, null, 2), 'utf-8') + } +} diff --git a/promptforge/src/storage/ReplayStore.ts b/promptforge/src/storage/ReplayStore.ts new file mode 100644 index 0000000..12f19dd --- /dev/null +++ b/promptforge/src/storage/ReplayStore.ts @@ -0,0 +1,87 @@ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import * as fs from 'fs' +import * as path from 'path' +import { CapturedReplay } from '../types/prompts' +import { CaptureReplayPayload } from '../types/messages' + +export class ReplayStore { + private static instance: ReplayStore + private context: vscode.ExtensionContext + + private constructor(context: vscode.ExtensionContext) { + this.context = context + } + + static initialize(context: vscode.ExtensionContext): void { + ReplayStore.instance = new ReplayStore(context) + } + + static getInstance(): ReplayStore { + if (!ReplayStore.instance) { + throw new Error('ReplayStore nao inicializado. Chame initialize() primeiro.') + } + return ReplayStore.instance + } + + private getReplaysDir(): string { + const workspaceFolders = vscode.workspace.workspaceFolders + if (!workspaceFolders) { + throw new Error('Nenhum workspace aberto') + } + const dir = path.join(workspaceFolders[0].uri.fsPath, '.promptforge', 'replays') + fs.mkdirSync(dir, { recursive: true }) + return dir + } + + async captureReplay(payload: CaptureReplayPayload): Promise { + const dir = this.getReplaysDir() + const id = crypto.randomUUID() + const replay: CapturedReplay = { + id, + capturedAt: new Date().toISOString(), + model: payload.model, + params: payload.params, + systemPrompt: payload.systemPrompt, + userInput: payload.userInput, + originalOutput: payload.originalOutput, + originalCostUSD: payload.originalCostUSD, + originalLatencyMs: payload.originalLatencyMs, + label: payload.label, + } + + const filePath = path.join(dir, `${id}.json`) + await fs.promises.writeFile(filePath, JSON.stringify(replay, null, 2), 'utf-8') + return id + } + + async loadReplays(): Promise { + const dir = this.getReplaysDir() + if (!fs.existsSync(dir)) return [] + const files = (await fs.promises.readdir(dir)).filter((f) => f.endsWith('.json')) + const replays: CapturedReplay[] = [] + for (const f of files) { + try { + const data = await fs.promises.readFile(path.join(dir, f), 'utf-8') + replays.push(JSON.parse(data) as CapturedReplay) + } catch { + // Ignorar arquivos corrompidos + } + } + return replays.sort( + (a, b) => new Date(b.capturedAt).getTime() - new Date(a.capturedAt).getTime(), + ) + } + + async getReplay(id: string): Promise { + const dir = this.getReplaysDir() + const filePath = path.join(dir, `${id}.json`) + if (!fs.existsSync(filePath)) return null + const data = await fs.promises.readFile(filePath, 'utf-8') + try { + return JSON.parse(data) as CapturedReplay + } catch { + throw new Error(`Arquivo de replay corrompido: ${filePath}`) + } + } +} diff --git a/promptforge/src/types/guidelines.ts b/promptforge/src/types/guidelines.ts new file mode 100644 index 0000000..74140f8 --- /dev/null +++ b/promptforge/src/types/guidelines.ts @@ -0,0 +1,11 @@ +export interface PromptGuidelines { + preferredStructure: 'xml-tags' | 'markdown-headers' | 'plain-instruction' | 'declarative' + systemPromptStyle: string + reasoningControl: string + boostPatterns: string[] + avoidPatterns: string[] + temperatureNote?: string + chainOfThoughtNote?: string + examplePosition?: 'before-instruction' | 'after-instruction' | 'inline' + maxTemperature?: number +} diff --git a/promptforge/src/types/messages.ts b/promptforge/src/types/messages.ts new file mode 100644 index 0000000..a89e6f7 --- /dev/null +++ b/promptforge/src/types/messages.ts @@ -0,0 +1,123 @@ +import { ModelParams, CostInfo } from './models' +import { SavedPrompt, CapturedReplay } from './prompts' + +// --- Payloads: Webview -> Extensao --- + +export interface GeneratePromptPayload { + userIntent: string + userContext: string + audience: string + useCase: string + targetModel: string + params: ModelParams + currentDraft?: string +} + +export interface RunTestPayload { + systemPrompt: string + userPromptTemplate: string + variables: Record + userInput: string + desiredOutput?: string + model: string + params: ModelParams +} + +export interface ABTestPayload { + promptA: { systemPrompt: string; userPromptTemplate: string; versionId: string } + promptB: { systemPrompt: string; userPromptTemplate: string; versionId?: string } + inputs: string[] + variables: Record + model: string + params: ModelParams +} + +export interface RefinePromptPayload { + systemPrompt: string + userPromptTemplate: string + lastInput: string + actualOutput: string + desiredOutput: string + model: string + params: ModelParams + guidelinesText: string +} + +export interface CaptureReplayPayload { + model: string + params: ModelParams + systemPrompt: string + userInput: string + originalOutput: string + originalCostUSD: number + originalLatencyMs: number + label?: string +} + +export interface SavePromptPayload { + promptId?: string + name: string + description?: string + systemPrompt: string + userPromptTemplate: string + variables: Record + model: string + params: ModelParams + tags: string[] +} + +export interface GenerateSnippetPayload { + language: 'typescript' | 'python' | 'curl' + provider: 'anthropic' | 'openai' | 'google' + model: string + params: ModelParams + systemPrompt: string + userInput: string +} + +export type WebviewMessage = + | { type: 'GENERATE_PROMPT'; payload: GeneratePromptPayload } + | { type: 'RUN_TEST'; payload: RunTestPayload } + | { type: 'RUN_AB_TEST'; payload: ABTestPayload } + | { type: 'REFINE_PROMPT'; payload: RefinePromptPayload } + | { type: 'CAPTURE_REPLAY'; payload: CaptureReplayPayload } + | { type: 'LOAD_REPLAYS'; payload: undefined } + | { type: 'SAVE_PROMPT'; payload: SavePromptPayload } + | { type: 'LOAD_LIBRARY'; payload: undefined } + | { type: 'DELETE_PROMPT'; payload: { promptId: string } } + | { type: 'GENERATE_SNIPPET'; payload: GenerateSnippetPayload } + | { type: 'CONFIGURE_KEYS'; payload: undefined } + +// --- Payloads: Extensao -> Webview --- + +export interface TestResultPayload { + runId: string + output: string + cost: CostInfo + latencyMs: number + semanticScore?: number +} + +export interface ABTestResultPayload { + inputs: string[] + resultsA: TestResultPayload[] + resultsB: TestResultPayload[] + summaryA: { avgCostUSD: number; avgLatencyMs: number; avgScore?: number } + summaryB: { avgCostUSD: number; avgLatencyMs: number; avgScore?: number } +} + +export type ExtensionMessage = + | { type: 'STREAM_CHUNK'; payload: { chunk: string; streamId: string } } + | { type: 'STREAM_DONE'; payload: { fullText: string; streamId: string; cost: CostInfo } } + | { type: 'TEST_RESULT'; payload: TestResultPayload } + | { type: 'AB_RESULT'; payload: ABTestResultPayload } + | { type: 'REFINE_RESULT'; payload: { fullText: string; streamId: string; cost: CostInfo } } + | { type: 'LIBRARY_DATA'; payload: { prompts: SavedPrompt[] } } + | { type: 'REPLAYS_DATA'; payload: { replays: CapturedReplay[] } } + | { type: 'SNIPPET_RESULT'; payload: { code: string; language: string } } + | { type: 'PROMPT_SAVED'; payload: { promptId: string } } + | { + type: 'KEYS_STATUS' + payload: { anthropic: boolean; openai: boolean; google: boolean; openrouter: boolean } + } + | { type: 'ERROR'; payload: { message: string; code?: string } } diff --git a/promptforge/src/types/models.ts b/promptforge/src/types/models.ts new file mode 100644 index 0000000..174ed32 --- /dev/null +++ b/promptforge/src/types/models.ts @@ -0,0 +1,88 @@ +export interface CompletionResult { + text: string + inputTokens: number + outputTokens: number + model: string + latencyMs: number +} + +export interface ModelParams { + model: string + temperature: number + maxTokens: number + topP?: number + reasoningEffort?: 'low' | 'medium' | 'high' | 'max' + thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high' +} + +export interface CostInfo { + inputTokens: number + outputTokens: number + inputCostUSD: number + outputCostUSD: number + totalCostUSD: number +} + +export interface ModelCatalogEntry { + id: string + label: string + tier: string + supportsThinking?: boolean + isOSeries?: boolean + supportsThinkingLevel?: boolean +} + +export const MODEL_CATALOG: Record = { + anthropic: [ + { id: 'claude-opus-4-6', label: 'Claude Opus 4.6', tier: 'flagship', supportsThinking: true }, + { + id: 'claude-sonnet-4-6', + label: 'Claude Sonnet 4.6', + tier: 'balanced', + supportsThinking: true, + }, + { id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5', tier: 'fast', supportsThinking: false }, + ], + openai: [ + { id: 'gpt-5.4', label: 'GPT-5.4', tier: 'flagship', isOSeries: false }, + { id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini', tier: 'balanced', isOSeries: false }, + { id: 'o3', label: 'o3', tier: 'reasoning', isOSeries: true }, + { id: 'o4-mini', label: 'o4-mini', tier: 'reasoning', isOSeries: true }, + ], + google: [ + { + id: 'gemini-3.1-pro-preview', + label: 'Gemini 3.1 Pro', + tier: 'flagship', + supportsThinkingLevel: true, + }, + { + id: 'gemini-2.5-flash', + label: 'Gemini 2.5 Flash', + tier: 'balanced', + supportsThinkingLevel: false, + }, + { + id: 'gemini-3.1-flash-lite-preview', + label: 'Gemini 3.1 Flash Lite', + tier: 'fast', + supportsThinkingLevel: true, + }, + ], + openrouter: [ + { id: 'openrouter/auto', label: 'OpenRouter Auto', tier: 'varied', isOSeries: false }, + ], +} + +export const DEFAULT_PARAMS: Record> = { + 'claude-opus-4-6': { temperature: 1.0, maxTokens: 4096, reasoningEffort: 'medium' }, + 'claude-sonnet-4-6': { temperature: 1.0, maxTokens: 4096, reasoningEffort: 'medium' }, + 'claude-haiku-4-5': { temperature: 1.0, maxTokens: 2048 }, + 'gpt-5.4': { temperature: 1.0, maxTokens: 4096 }, + 'gpt-5.4-mini': { temperature: 1.0, maxTokens: 4096 }, + o3: { temperature: 1.0, maxTokens: 8192 }, + 'o4-mini': { temperature: 1.0, maxTokens: 8192 }, + 'gemini-3.1-pro-preview': { temperature: 1.0, maxTokens: 8192, thinkingLevel: 'medium' }, + 'gemini-2.5-flash': { temperature: 1.0, maxTokens: 4096 }, + 'gemini-3.1-flash-lite-preview': { temperature: 1.0, maxTokens: 2048 }, +} diff --git a/promptforge/src/types/prompts.ts b/promptforge/src/types/prompts.ts new file mode 100644 index 0000000..47b9629 --- /dev/null +++ b/promptforge/src/types/prompts.ts @@ -0,0 +1,57 @@ +import { ModelParams } from './models' + +export const SCHEMA_VERSION = 1 + +export interface SavedPrompt { + schemaVersion: number + id: string + name: string + description?: string + createdAt: string + updatedAt: string + versions: PromptVersion[] + tags: string[] + category?: string +} + +export interface PromptVersion { + versionId: string + versionNumber: string + createdAt: string + model: string + params: ModelParams + systemPrompt: string + userPromptTemplate: string + variables: Record + status: 'draft' | 'staging' | 'production' + testHistory: TestRun[] + avgCostUSD: number + totalRuns: number +} + +export interface TestRun { + runId: string + runAt: string + model: string + input: string + output: string + desiredOutput?: string + semanticScore?: number + costUSD: number + latencyMs: number + feedback?: 'positive' | 'negative' + feedbackNote?: string +} + +export interface CapturedReplay { + id: string + capturedAt: string + model: string + params: ModelParams + systemPrompt: string + userInput: string + originalOutput: string + originalCostUSD: number + originalLatencyMs: number + label?: string +} diff --git a/promptforge/src/utils/snippetGenerator.ts b/promptforge/src/utils/snippetGenerator.ts new file mode 100644 index 0000000..9d797bb --- /dev/null +++ b/promptforge/src/utils/snippetGenerator.ts @@ -0,0 +1,137 @@ +import { GenerateSnippetPayload } from '../types/messages' + +const SNIPPET_TEMPLATES: Record> = { + typescript: { + anthropic: `import Anthropic from '@anthropic-ai/sdk' + +const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }) + +const response = await client.messages.create({ + model: '{{model}}', + max_tokens: {{maxTokens}}, + system: \`{{systemPrompt}}\`, + messages: [{ role: 'user', content: '{{userInput}}' }], +}) + +console.log(response.content[0].text)`, + + openai: `import OpenAI from 'openai' + +const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }) + +// Responses API — API primaria para modelos GPT-5.x (openai >= 6.x) +const response = await client.responses.create({ + model: '{{model}}', + max_output_tokens: {{maxTokens}}, + instructions: \`{{systemPrompt}}\`, + input: '{{userInput}}', +}) + +console.log(response.output_text)`, + + google: `import { GoogleGenerativeAI } from '@google/generative-ai' + +const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!) +const model = genAI.getGenerativeModel({ + model: '{{model}}', + systemInstruction: \`{{systemPrompt}}\`, +}) + +const result = await model.generateContent('{{userInput}}') +console.log(result.response.text())`, + }, + + python: { + anthropic: `import anthropic +import os + +client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) + +message = client.messages.create( + model="{{model}}", + max_tokens={{maxTokens}}, + system="""{{systemPrompt}}""", + messages=[{"role": "user", "content": "{{userInput}}"}], +) + +print(message.content[0].text)`, + + openai: `from openai import OpenAI +import os + +client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) + +# Responses API — API primaria para modelos GPT-5.x (openai >= 1.66.0 Python) +response = client.responses.create( + model="{{model}}", + max_output_tokens={{maxTokens}}, + instructions="""{{systemPrompt}}""", + input="{{userInput}}", +) + +print(response.output_text)`, + + google: `import google.generativeai as genai +import os + +genai.configure(api_key=os.environ.get("GEMINI_API_KEY")) + +model = genai.GenerativeModel( + model_name="{{model}}", + system_instruction="""{{systemPrompt}}""", +) + +response = model.generate_content("{{userInput}}") +print(response.text)`, + }, + + curl: { + anthropic: `curl https://api.anthropic.com/v1/messages \\ + -H "x-api-key: $ANTHROPIC_API_KEY" \\ + -H "anthropic-version: 2023-06-01" \\ + -H "content-type: application/json" \\ + -d '{ + "model": "{{model}}", + "max_tokens": {{maxTokens}}, + "system": "{{systemPromptEscaped}}", + "messages": [{"role": "user", "content": "{{userInputEscaped}}"}] + }'`, + + openai: `# Responses API — endpoint primario para modelos GPT-5.x +curl https://api.openai.com/v1/responses \\ + -H "Authorization: Bearer $OPENAI_API_KEY" \\ + -H "Content-Type: application/json" \\ + -d '{ + "model": "{{model}}", + "max_output_tokens": {{maxTokens}}, + "instructions": "{{systemPromptEscaped}}", + "input": "{{userInputEscaped}}" + }'`, + + google: `curl "https://generativelanguage.googleapis.com/v1beta/models/{{model}}:generateContent?key=$GEMINI_API_KEY" \\ + -H "Content-Type: application/json" \\ + -d '{ + "system_instruction": {"parts": [{"text": "{{systemPromptEscaped}}"}]}, + "contents": [{"parts": [{"text": "{{userInputEscaped}}"}]}] + }'`, + }, +} + +function escapeForJSON(str: string): string { + return JSON.stringify(str).slice(1, -1) +} + +export function generateSnippet(payload: GenerateSnippetPayload): string { + const template = SNIPPET_TEMPLATES[payload.language]?.[payload.provider] + if (!template) { + throw new Error(`Template nao encontrado: ${payload.language}/${payload.provider}`) + } + + return template + .replace(/\{\{model\}\}/g, payload.model) + .replace(/\{\{maxTokens\}\}/g, String(payload.params.maxTokens)) + .replace(/\{\{systemPrompt\}\}/g, payload.systemPrompt) + .replace(/\{\{userInput\}\}/g, payload.userInput) + .replace(/\{\{systemPromptEscaped\}\}/g, escapeForJSON(payload.systemPrompt)) + .replace(/\{\{userInputEscaped\}\}/g, escapeForJSON(payload.userInput)) +} diff --git a/promptforge/src/utils/streamBridge.ts b/promptforge/src/utils/streamBridge.ts new file mode 100644 index 0000000..411557e --- /dev/null +++ b/promptforge/src/utils/streamBridge.ts @@ -0,0 +1,36 @@ +import * as vscode from 'vscode' +import { ExtensionMessage, } from '../types/messages' +import { CostInfo } from '../types/models' + +export async function streamToWebview( + generator: AsyncGenerator, + panel: vscode.WebviewPanel, + streamId: string, + calculateCost: (input: number, output: number) => CostInfo, + inputTokens: number, +): Promise { + let fullText = '' + let outputTokens = 0 + + try { + for await (const chunk of generator) { + fullText += chunk + outputTokens += Math.ceil(chunk.length / 4) + + panel.webview.postMessage({ + type: 'STREAM_CHUNK', + payload: { chunk, streamId }, + } satisfies ExtensionMessage) + } + + panel.webview.postMessage({ + type: 'STREAM_DONE', + payload: { fullText, streamId, cost: calculateCost(inputTokens, outputTokens) }, + } satisfies ExtensionMessage) + } catch (error) { + panel.webview.postMessage({ + type: 'ERROR', + payload: { message: String(error), code: 'STREAM_ERROR' }, + } satisfies ExtensionMessage) + } +} diff --git a/promptforge/src/utils/tokenCounter.ts b/promptforge/src/utils/tokenCounter.ts new file mode 100644 index 0000000..29ff893 --- /dev/null +++ b/promptforge/src/utils/tokenCounter.ts @@ -0,0 +1,26 @@ +import { CostInfo } from '../types/models' + +export const PRICING_USD_PER_1M: Record = { + 'claude-opus-4-6': { input: 5.0, output: 25.0 }, + 'claude-sonnet-4-6': { input: 3.0, output: 15.0 }, + 'claude-haiku-4-5': { input: 1.0, output: 5.0 }, + 'gpt-5.4': { input: 2.5, output: 15.0 }, + 'gpt-5.4-mini': { input: 0.4, output: 1.6 }, + o3: { input: 10.0, output: 40.0 }, + 'o4-mini': { input: 1.1, output: 4.4 }, + 'gemini-3.1-pro-preview': { input: 2.0, output: 12.0 }, + 'gemini-2.5-flash': { input: 0.3, output: 2.5 }, + 'gemini-3.1-flash-lite-preview': { input: 0.1, output: 0.6 }, +} + +export function calculateCost(model: string, inputTokens: number, outputTokens: number): CostInfo { + const price = PRICING_USD_PER_1M[model] ?? { input: 1.0, output: 5.0 } + return { + inputTokens, + outputTokens, + inputCostUSD: (inputTokens / 1_000_000) * price.input, + outputCostUSD: (outputTokens / 1_000_000) * price.output, + totalCostUSD: + (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output, + } +} diff --git a/promptforge/src/utils/variableParser.ts b/promptforge/src/utils/variableParser.ts new file mode 100644 index 0000000..be47d77 --- /dev/null +++ b/promptforge/src/utils/variableParser.ts @@ -0,0 +1,36 @@ +export interface ParsedVariable { + name: string + defaultValue?: string +} + +const VARIABLE_REGEX = /\{\{([^}:]+)(?::([^}]*))?\}\}/g + +export function parseVariables(template: string): ParsedVariable[] { + const variables: ParsedVariable[] = [] + const seen = new Set() + let match: RegExpExecArray | null + + const regex = new RegExp(VARIABLE_REGEX.source, VARIABLE_REGEX.flags) + while ((match = regex.exec(template)) !== null) { + const name = match[1].trim() + if (!seen.has(name)) { + seen.add(name) + variables.push({ + name, + defaultValue: match[2]?.trim(), + }) + } + } + + return variables +} + +export function resolveVariables( + template: string, + values: Record, +): string { + return template.replace(VARIABLE_REGEX, (_match, name: string, defaultValue?: string) => { + const trimmedName = name.trim() + return values[trimmedName] ?? defaultValue?.trim() ?? `{{${trimmedName}}}` + }) +} diff --git a/promptforge/tailwind.config.js b/promptforge/tailwind.config.js new file mode 100644 index 0000000..3e5f0d3 --- /dev/null +++ b/promptforge/tailwind.config.js @@ -0,0 +1,30 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./webview/src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + forge: { + bg: '#1a1a1a', + 'bg-deep': '#0d0d0d', + accent: '#f97316', + 'accent-light': '#fb923c', + 'accent-dim': '#c2410c', + surface: '#262626', + 'surface-light': '#333333', + border: '#404040', + text: '#e5e5e5', + 'text-dim': '#a3a3a3', + 'text-muted': '#737373', + }, + }, + fontFamily: { + code: ["'JetBrains Mono'", "'Cascadia Code'", 'monospace'], + }, + borderRadius: { + forge: '6px', + }, + }, + }, + plugins: [], +} diff --git a/promptforge/tests/integration/index.ts b/promptforge/tests/integration/index.ts new file mode 100644 index 0000000..5a5805b --- /dev/null +++ b/promptforge/tests/integration/index.ts @@ -0,0 +1,59 @@ +/** + * Testes de integracao E2E para a extensao PromptForge. + * Executa dentro do VS Code Extension Host via @vscode/test-electron. + * + * Para rodar: npx @vscode/test-electron --extensionDevelopmentPath=. tests/integration/index.ts + * + * Nota: Estes testes requerem o VS Code instalado e nao rodam em CI sem headless. + * Para CI, usar os testes unitarios em tests/unit/ que cobrem toda a logica de negocio. + */ + +import * as assert from 'assert' +import * as vscode from 'vscode' + +suite('PromptForge Extension Tests', () => { + test('Extensao deve ativar', async () => { + const ext = vscode.extensions.getExtension('promptforge-dev.promptforge') + assert.ok(ext, 'Extensao nao encontrada') + if (!ext.isActive) { + await ext.activate() + } + assert.ok(ext.isActive, 'Extensao nao ativou') + }) + + test('Comandos devem estar registrados', async () => { + const commands = await vscode.commands.getCommands(true) + assert.ok(commands.includes('promptforge.newPrompt'), 'promptforge.newPrompt nao registrado') + assert.ok(commands.includes('promptforge.testPrompt'), 'promptforge.testPrompt nao registrado') + assert.ok( + commands.includes('promptforge.openLibrary'), + 'promptforge.openLibrary nao registrado', + ) + assert.ok( + commands.includes('promptforge.configureKeys'), + 'promptforge.configureKeys nao registrado', + ) + }) + + test('promptforge.newPrompt deve abrir EditorPanel', async () => { + await vscode.commands.executeCommand('promptforge.newPrompt') + // O panel abre em background — verificar que nao lanca erro + assert.ok(true, 'Comando executou sem erro') + }) + + test('promptforge.testPrompt deve abrir TestPanel', async () => { + await vscode.commands.executeCommand('promptforge.testPrompt') + assert.ok(true, 'Comando executou sem erro') + }) + + test('promptforge.openLibrary deve abrir LibraryPanel', async () => { + await vscode.commands.executeCommand('promptforge.openLibrary') + assert.ok(true, 'Comando executou sem erro') + }) + + test('Configuracao padrao deve ser claude-opus-4-6', () => { + const config = vscode.workspace.getConfiguration('promptforge') + const defaultModel = config.get('defaultModel') + assert.strictEqual(defaultModel, 'claude-opus-4-6') + }) +}) diff --git a/promptforge/tests/unit/bestPractices.extended.test.ts b/promptforge/tests/unit/bestPractices.extended.test.ts new file mode 100644 index 0000000..e2e1975 --- /dev/null +++ b/promptforge/tests/unit/bestPractices.extended.test.ts @@ -0,0 +1,189 @@ +import { describe, it, expect } from 'vitest' +import { claudeGuidelines } from '../../src/bestPractices/claude' +import { openaiGPT5Guidelines, openaiOSeriesGuidelines } from '../../src/bestPractices/openai' +import { gemini3Guidelines, gemini25Guidelines } from '../../src/bestPractices/google' +import { getGuidelines } from '../../src/bestPractices/index' +import { buildCowriterSystemPrompt } from '../../src/bestPractices/cowriterSystemPrompt' + +describe('claudeGuidelines', () => { + it('deve ter estrutura preferida xml-tags', () => { + expect(claudeGuidelines.preferredStructure).toBe('xml-tags') + }) + + it('deve ter boostPatterns nao vazio', () => { + expect(claudeGuidelines.boostPatterns.length).toBeGreaterThan(0) + }) + + it('deve ter avoidPatterns nao vazio', () => { + expect(claudeGuidelines.avoidPatterns.length).toBeGreaterThan(0) + }) + + it('deve incluir xml-tags nos boostPatterns', () => { + expect(claudeGuidelines.boostPatterns.some((p) => p.includes('xml'))).toBe(true) + }) + + it('deve ter chainOfThoughtNote definido', () => { + expect(claudeGuidelines.chainOfThoughtNote).toBeDefined() + }) + + it('deve ter examplePosition before-instruction', () => { + expect(claudeGuidelines.examplePosition).toBe('before-instruction') + }) +}) + +describe('openaiGPT5Guidelines', () => { + it('deve ter estrutura preferida markdown-headers', () => { + expect(openaiGPT5Guidelines.preferredStructure).toBe('markdown-headers') + }) + + it('deve ter boostPatterns nao vazio', () => { + expect(openaiGPT5Guidelines.boostPatterns.length).toBeGreaterThan(0) + }) + + it('deve evitar chain-of-thought explicito', () => { + expect(openaiGPT5Guidelines.avoidPatterns).toContain('explicit-chain-of-thought-instructions') + }) + + it('deve ter nota sobre raciocinio nativo', () => { + expect(openaiGPT5Guidelines.chainOfThoughtNote).toContain('nativo') + }) +}) + +describe('openaiOSeriesGuidelines', () => { + it('deve ter estrutura plain-instruction', () => { + expect(openaiOSeriesGuidelines.preferredStructure).toBe('plain-instruction') + }) + + it('deve ter maxTemperature 1.0', () => { + expect(openaiOSeriesGuidelines.maxTemperature).toBe(1.0) + }) + + it('deve evitar step-by-step', () => { + expect(openaiOSeriesGuidelines.avoidPatterns).toContain('step-by-step-instructions') + }) + + it('deve evitar ajuste de temperatura', () => { + expect(openaiOSeriesGuidelines.avoidPatterns).toContain('temperature-adjustment') + }) + + it('deve ter nota sobre NUNCA instrua passo a passo', () => { + expect(openaiOSeriesGuidelines.chainOfThoughtNote).toContain('NUNCA') + }) +}) + +describe('gemini3Guidelines', () => { + it('deve ter estrutura declarative', () => { + expect(gemini3Guidelines.preferredStructure).toBe('declarative') + }) + + it('deve ter temperatureNote definido', () => { + expect(gemini3Guidelines.temperatureNote).toBeDefined() + expect(gemini3Guidelines.temperatureNote).toContain('CRITICO') + }) + + it('deve ter maxTemperature 2.0', () => { + expect(gemini3Guidelines.maxTemperature).toBe(2.0) + }) + + it('deve ter chainOfThoughtNote sobre thinking_level', () => { + expect(gemini3Guidelines.chainOfThoughtNote).toContain('thinking_level') + }) + + it('deve ter boostPatterns nao vazio', () => { + expect(gemini3Guidelines.boostPatterns.length).toBeGreaterThan(0) + }) +}) + +describe('gemini25Guidelines', () => { + it('deve ter reasoningControl contendo thinking-budget', () => { + expect(gemini25Guidelines.reasoningControl).toContain('thinking-budget') + }) + + it('deve ter chainOfThoughtNote diferente de Gemini 3', () => { + expect(gemini25Guidelines.chainOfThoughtNote).toContain('thinking_budget') + }) + + it('deve ter estrutura declarative', () => { + expect(gemini25Guidelines.preferredStructure).toBe('declarative') + }) +}) + +describe('getGuidelines - cobertura completa', () => { + it('deve rotear claude-sonnet-4-6 para claudeGuidelines', () => { + expect(getGuidelines('claude-sonnet-4-6')).toBe(claudeGuidelines) + }) + + it('deve rotear claude-haiku-4-5 para claudeGuidelines', () => { + expect(getGuidelines('claude-haiku-4-5')).toBe(claudeGuidelines) + }) + + it('deve rotear o4-mini para openaiOSeriesGuidelines', () => { + expect(getGuidelines('o4-mini')).toBe(openaiOSeriesGuidelines) + }) + + it('deve rotear gpt-5.4-mini para openaiGPT5Guidelines', () => { + expect(getGuidelines('gpt-5.4-mini')).toBe(openaiGPT5Guidelines) + }) + + it('deve rotear gemini-3.1-flash-lite-preview para gemini3Guidelines', () => { + expect(getGuidelines('gemini-3.1-flash-lite-preview')).toBe(gemini3Guidelines) + }) +}) + +describe('buildCowriterSystemPrompt - cobertura completa', () => { + it('deve incluir todas as secoes de contexto do usuario', () => { + const prompt = buildCowriterSystemPrompt( + 'claude-opus-4-6', + claudeGuidelines, + 'Gerar emails', + 'Equipe de vendas', + 'Clientes B2B', + 'Email marketing', + ) + expect(prompt).toContain('Gerar emails') + expect(prompt).toContain('Equipe de vendas') + expect(prompt).toContain('Clientes B2B') + expect(prompt).toContain('Email marketing') + }) + + it('deve incluir todas as 10 regras de co-autoria', () => { + const prompt = buildCowriterSystemPrompt( + 'gpt-5.4', + openaiGPT5Guidelines, + 'intent', + 'context', + 'audience', + 'useCase', + ) + for (let i = 1; i <= 10; i++) { + expect(prompt).toContain(`${i}.`) + } + }) + + it('deve incluir variavel {{nome_variavel}}', () => { + const prompt = buildCowriterSystemPrompt( + 'gemini-2.5-flash', + gemini25Guidelines, + 'i', 'c', 'a', 'u', + ) + expect(prompt).toContain('{{nome_variavel}}') + }) + + it('deve incluir temperatureNote quando disponivel', () => { + const prompt = buildCowriterSystemPrompt( + 'gemini-3.1-pro-preview', + gemini3Guidelines, + 'i', 'c', 'a', 'u', + ) + expect(prompt).toContain('CRITICO') + }) + + it('deve omitir temperatureNote quando nao disponivel', () => { + const prompt = buildCowriterSystemPrompt( + 'claude-opus-4-6', + claudeGuidelines, + 'i', 'c', 'a', 'u', + ) + expect(prompt).not.toContain('Nota critica sobre temperatura') + }) +}) diff --git a/promptforge/tests/unit/bestPractices.test.ts b/promptforge/tests/unit/bestPractices.test.ts new file mode 100644 index 0000000..02c71db --- /dev/null +++ b/promptforge/tests/unit/bestPractices.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'vitest' +import { getGuidelines } from '../../src/bestPractices/index' +import { buildCowriterSystemPrompt } from '../../src/bestPractices/cowriterSystemPrompt' + +describe('getGuidelines', () => { + it('deve retornar guidelines Claude para modelos claude-*', () => { + const g = getGuidelines('claude-opus-4-6') + expect(g.preferredStructure).toBe('xml-tags') + }) + + it('deve retornar guidelines o-series para o3', () => { + const g = getGuidelines('o3') + expect(g.preferredStructure).toBe('plain-instruction') + expect(g.maxTemperature).toBe(1.0) + }) + + it('deve retornar guidelines GPT-5 para gpt-*', () => { + const g = getGuidelines('gpt-5.4') + expect(g.preferredStructure).toBe('markdown-headers') + }) + + it('deve retornar guidelines Gemini 3 para gemini-3.*', () => { + const g = getGuidelines('gemini-3.1-pro-preview') + expect(g.preferredStructure).toBe('declarative') + expect(g.temperatureNote).toBeDefined() + }) + + it('deve retornar guidelines Gemini 2.5 para gemini-2.*', () => { + const g = getGuidelines('gemini-2.5-flash') + expect(g.reasoningControl).toBe('use-thinking-budget-integer-tokens') + }) + + it('deve retornar fallback para modelo desconhecido', () => { + const g = getGuidelines('modelo-futuro') + expect(g).toBeDefined() + expect(g.preferredStructure).toBeDefined() + }) +}) + +describe('buildCowriterSystemPrompt', () => { + it('deve incluir modelo alvo', () => { + const guidelines = getGuidelines('claude-opus-4-6') + const prompt = buildCowriterSystemPrompt( + 'claude-opus-4-6', + guidelines, + 'Gerar resumos', + 'Artigos cientificos', + 'Pesquisadores', + 'Resumo automatico', + ) + expect(prompt).toContain('claude-opus-4-6') + expect(prompt).toContain('xml-tags') + expect(prompt).toContain('Gerar resumos') + expect(prompt).toContain('{{nome_variavel}}') + }) + + it('deve incluir nota de CoT quando disponivel', () => { + const guidelines = getGuidelines('o3') + const prompt = buildCowriterSystemPrompt( + 'o3', + guidelines, + 'Resolver problema', + 'Matematica', + 'Estudantes', + 'Tutoria', + ) + expect(prompt).toContain('NUNCA instrua o-series') + }) +}) diff --git a/promptforge/tests/unit/commands.test.ts b/promptforge/tests/unit/commands.test.ts new file mode 100644 index 0000000..00acc94 --- /dev/null +++ b/promptforge/tests/unit/commands.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi } from 'vitest' + +vi.mock('vscode', () => ({ + Uri: { joinPath: (...p: unknown[]) => p.join('/') }, + workspace: { workspaceFolders: [{ uri: { fsPath: '/tmp' } }] }, +})) + +vi.mock('../../src/panels/EditorPanel', () => ({ + EditorPanel: { createOrShow: vi.fn() }, +})) +vi.mock('../../src/panels/TestPanel', () => ({ + TestPanel: { createOrShow: vi.fn() }, +})) +vi.mock('../../src/panels/LibraryPanel', () => ({ + LibraryPanel: { createOrShow: vi.fn() }, +})) + +import { newPromptCommand } from '../../src/commands/newPrompt' +import { testPromptCommand } from '../../src/commands/testPrompt' +import { openLibraryCommand } from '../../src/commands/openLibrary' +import { EditorPanel } from '../../src/panels/EditorPanel' +import { TestPanel } from '../../src/panels/TestPanel' +import { LibraryPanel } from '../../src/panels/LibraryPanel' + +describe('newPromptCommand', () => { + it('deve chamar EditorPanel.createOrShow com extensionUri', () => { + const uri = { fsPath: '/ext' } as any + newPromptCommand(uri) + expect(EditorPanel.createOrShow).toHaveBeenCalledWith(uri) + }) +}) + +describe('testPromptCommand', () => { + it('deve chamar TestPanel.createOrShow com extensionUri', () => { + const uri = { fsPath: '/ext' } as any + testPromptCommand(uri) + expect(TestPanel.createOrShow).toHaveBeenCalledWith(uri) + }) +}) + +describe('openLibraryCommand', () => { + it('deve chamar LibraryPanel.createOrShow com extensionUri', () => { + const uri = { fsPath: '/ext' } as any + openLibraryCommand(uri) + expect(LibraryPanel.createOrShow).toHaveBeenCalledWith(uri) + }) +}) diff --git a/promptforge/tests/unit/configureKeys.extended.test.ts b/promptforge/tests/unit/configureKeys.extended.test.ts new file mode 100644 index 0000000..859325b --- /dev/null +++ b/promptforge/tests/unit/configureKeys.extended.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockShowQuickPick = vi.fn() +const mockShowInputBox = vi.fn() +const mockShowInfoMessage = vi.fn() +const mockSecretsStore = vi.fn() +const mockSecretsGet = vi.fn() + +vi.mock('vscode', () => ({ + window: { + showQuickPick: (...args: unknown[]) => mockShowQuickPick(...args), + showInputBox: (...args: unknown[]) => mockShowInputBox(...args), + showInformationMessage: (...args: unknown[]) => mockShowInfoMessage(...args), + }, +})) + +import { + setExtensionContext, + configureKeysCommand, + getApiKey, +} from '../../src/commands/configureKeys' + +beforeEach(() => { + vi.clearAllMocks() + mockSecretsGet.mockReset() + setExtensionContext({ + secrets: { store: mockSecretsStore, get: mockSecretsGet }, + subscriptions: [], + } as any) + delete process.env.ANTHROPIC_API_KEY + delete process.env.OPENAI_API_KEY + delete process.env.GEMINI_API_KEY + delete process.env.OPENROUTER_API_KEY +}) + +describe('configureKeysCommand', () => { + it('deve abortar se usuario cancelar selecao de provider', async () => { + mockShowQuickPick.mockResolvedValue(undefined) + await configureKeysCommand() + expect(mockShowInputBox).not.toHaveBeenCalled() + expect(mockSecretsStore).not.toHaveBeenCalled() + }) + + it('deve abortar se usuario cancelar input de key', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'Anthropic (Claude)', value: 'anthropic' }) + mockShowInputBox.mockResolvedValue(undefined) + await configureKeysCommand() + expect(mockSecretsStore).not.toHaveBeenCalled() + }) + + it('deve salvar key Anthropic no SecretStorage', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'Anthropic (Claude)', value: 'anthropic' }) + mockShowInputBox.mockResolvedValue('sk-ant-test123') + await configureKeysCommand() + expect(mockSecretsStore).toHaveBeenCalledWith('promptforge.key.anthropic', 'sk-ant-test123') + expect(mockShowInfoMessage).toHaveBeenCalledWith(expect.stringContaining('salva com sucesso')) + }) + + it('deve salvar key OpenAI no SecretStorage', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'OpenAI (GPT / o-series)', value: 'openai' }) + mockShowInputBox.mockResolvedValue('sk-openai-key') + await configureKeysCommand() + expect(mockSecretsStore).toHaveBeenCalledWith('promptforge.key.openai', 'sk-openai-key') + }) + + it('deve salvar key Google no SecretStorage', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'Google (Gemini)', value: 'google' }) + mockShowInputBox.mockResolvedValue('AIza-key') + await configureKeysCommand() + expect(mockSecretsStore).toHaveBeenCalledWith('promptforge.key.google', 'AIza-key') + }) + + it('deve salvar key OpenRouter no SecretStorage', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'OpenRouter', value: 'openrouter' }) + mockShowInputBox.mockResolvedValue('sk-or-key') + await configureKeysCommand() + expect(mockSecretsStore).toHaveBeenCalledWith('promptforge.key.openrouter', 'sk-or-key') + }) + + it('deve usar password:true no inputBox', async () => { + mockShowQuickPick.mockResolvedValue({ label: 'Anthropic (Claude)', value: 'anthropic' }) + mockShowInputBox.mockResolvedValue('key') + await configureKeysCommand() + expect(mockShowInputBox).toHaveBeenCalledWith(expect.objectContaining({ password: true })) + }) +}) + +describe('getApiKey - routing completo', () => { + it('deve rotear o4-mini para secretKey openai', async () => { + mockSecretsGet.mockResolvedValue('sk-o4-mini') + const key = await getApiKey('o4-mini') + expect(key).toBe('sk-o4-mini') + expect(mockSecretsGet).toHaveBeenCalledWith('promptforge.key.openai') + }) + + it('deve rotear claude-sonnet-4-6 para secretKey anthropic', async () => { + mockSecretsGet.mockResolvedValue('sk-sonnet') + const key = await getApiKey('claude-sonnet-4-6') + expect(key).toBe('sk-sonnet') + }) + + it('deve rotear gemini-3.1-flash-lite-preview para secretKey google', async () => { + mockSecretsGet.mockResolvedValue('AIza') + const key = await getApiKey('gemini-3.1-flash-lite-preview') + expect(key).toBe('AIza') + }) + + it('deve usar fallback env var para OpenRouter', async () => { + mockSecretsGet.mockResolvedValue(undefined) + process.env.OPENROUTER_API_KEY = 'sk-or-env' + const key = await getApiKey('openrouter/auto') + expect(key).toBe('sk-or-env') + }) + + it('deve priorizar SecretStorage sobre env var', async () => { + mockSecretsGet.mockResolvedValue('sk-from-secrets') + process.env.ANTHROPIC_API_KEY = 'sk-from-env' + const key = await getApiKey('claude-opus-4-6') + expect(key).toBe('sk-from-secrets') + }) +}) diff --git a/promptforge/tests/unit/configureKeys.test.ts b/promptforge/tests/unit/configureKeys.test.ts new file mode 100644 index 0000000..bc2567a --- /dev/null +++ b/promptforge/tests/unit/configureKeys.test.ts @@ -0,0 +1,129 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockSecrets = { + store: vi.fn(), + get: vi.fn(), +} + +vi.mock('vscode', () => ({ + window: { + showQuickPick: vi.fn(), + showInputBox: vi.fn(), + showInformationMessage: vi.fn(), + }, +})) + +import { setExtensionContext, getApiKey, getKeysStatus } from '../../src/commands/configureKeys' + +beforeEach(() => { + vi.clearAllMocks() + mockSecrets.get.mockReset() + + setExtensionContext({ + secrets: mockSecrets, + subscriptions: [], + } as any) + + // Limpar env vars + delete process.env.ANTHROPIC_API_KEY + delete process.env.OPENAI_API_KEY + delete process.env.GEMINI_API_KEY + delete process.env.OPENROUTER_API_KEY +}) + +describe('getApiKey', () => { + it('deve retornar key do SecretStorage para Claude', async () => { + mockSecrets.get.mockResolvedValue('sk-ant-test') + const key = await getApiKey('claude-opus-4-6') + expect(key).toBe('sk-ant-test') + expect(mockSecrets.get).toHaveBeenCalledWith('promptforge.key.anthropic') + }) + + it('deve retornar key do SecretStorage para GPT', async () => { + mockSecrets.get.mockResolvedValue('sk-openai-test') + const key = await getApiKey('gpt-5.4') + expect(key).toBe('sk-openai-test') + expect(mockSecrets.get).toHaveBeenCalledWith('promptforge.key.openai') + }) + + it('deve retornar key do SecretStorage para o3', async () => { + mockSecrets.get.mockResolvedValue('sk-o3-test') + const key = await getApiKey('o3') + expect(key).toBe('sk-o3-test') + expect(mockSecrets.get).toHaveBeenCalledWith('promptforge.key.openai') + }) + + it('deve retornar key do SecretStorage para Gemini', async () => { + mockSecrets.get.mockResolvedValue('AIza-test') + const key = await getApiKey('gemini-3.1-pro-preview') + expect(key).toBe('AIza-test') + expect(mockSecrets.get).toHaveBeenCalledWith('promptforge.key.google') + }) + + it('deve retornar key do SecretStorage para OpenRouter', async () => { + mockSecrets.get.mockResolvedValue('sk-or-test') + const key = await getApiKey('openrouter/auto') + expect(key).toBe('sk-or-test') + expect(mockSecrets.get).toHaveBeenCalledWith('promptforge.key.openrouter') + }) + + it('deve usar env var como fallback para Anthropic', async () => { + mockSecrets.get.mockResolvedValue(undefined) + process.env.ANTHROPIC_API_KEY = 'sk-env-anthropic' + const key = await getApiKey('claude-opus-4-6') + expect(key).toBe('sk-env-anthropic') + }) + + it('deve usar env var como fallback para OpenAI', async () => { + mockSecrets.get.mockResolvedValue(undefined) + process.env.OPENAI_API_KEY = 'sk-env-openai' + const key = await getApiKey('gpt-5.4') + expect(key).toBe('sk-env-openai') + }) + + it('deve usar env var como fallback para Gemini', async () => { + mockSecrets.get.mockResolvedValue(undefined) + process.env.GEMINI_API_KEY = 'AIza-env' + const key = await getApiKey('gemini-2.5-flash') + expect(key).toBe('AIza-env') + }) + + it('deve retornar undefined sem key e sem env var', async () => { + mockSecrets.get.mockResolvedValue(undefined) + const key = await getApiKey('claude-opus-4-6') + expect(key).toBeUndefined() + }) + + it('deve retornar undefined para modelo desconhecido', async () => { + const key = await getApiKey('modelo-futuro') + expect(key).toBeUndefined() + }) +}) + +describe('getKeysStatus', () => { + it('deve retornar status correto com todas as keys', async () => { + mockSecrets.get.mockResolvedValue('some-key') + const status = await getKeysStatus() + expect(status.anthropic).toBe(true) + expect(status.openai).toBe(true) + expect(status.google).toBe(true) + expect(status.openrouter).toBe(true) + }) + + it('deve retornar false quando nenhuma key configurada', async () => { + mockSecrets.get.mockResolvedValue(undefined) + const status = await getKeysStatus() + expect(status.anthropic).toBe(false) + expect(status.openai).toBe(false) + expect(status.google).toBe(false) + expect(status.openrouter).toBe(false) + }) + + it('deve considerar env vars no status', async () => { + mockSecrets.get.mockResolvedValue(undefined) + process.env.ANTHROPIC_API_KEY = 'sk-env' + const status = await getKeysStatus() + expect(status.anthropic).toBe(true) + expect(status.openai).toBe(false) + }) +}) diff --git a/promptforge/tests/unit/editorPanel.test.ts b/promptforge/tests/unit/editorPanel.test.ts new file mode 100644 index 0000000..2ded76e --- /dev/null +++ b/promptforge/tests/unit/editorPanel.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mocks = vi.hoisted(() => ({ + postMessage: vi.fn(), + onDidReceiveMessage: vi.fn(), + onDidDispose: vi.fn(), + createWebviewPanel: vi.fn(), + executeCommand: vi.fn(), + html: '', +})) + +vi.mock('vscode', () => ({ + window: { + activeTextEditor: undefined, + createWebviewPanel: (...args: unknown[]) => { + mocks.createWebviewPanel(...args) + return { + webview: { + set html(val: string) { mocks.html = val }, + get html() { return mocks.html }, + postMessage: mocks.postMessage, + onDidReceiveMessage: mocks.onDidReceiveMessage, + asWebviewUri: (u: any) => u?.toString?.() ?? String(u), + cspSource: 'csp', + }, + onDidDispose: mocks.onDidDispose, + reveal: vi.fn(), + dispose: vi.fn(), + } + }, + }, + commands: { executeCommand: mocks.executeCommand }, + ViewColumn: { One: 1 }, + Uri: { joinPath: (...p: any[]) => ({ fsPath: p.map((x: any) => x?.fsPath ?? x).join('/'), toString: () => p.join('/') }) }, + workspace: { workspaceFolders: [{ uri: { fsPath: '/tmp' } }] }, +})) + +vi.mock('../../src/bestPractices/index', () => ({ + getGuidelines: vi.fn(() => ({ preferredStructure: 'xml-tags', systemPromptStyle: '', reasoningControl: '', boostPatterns: [], avoidPatterns: [] })), +})) +vi.mock('../../src/bestPractices/cowriterSystemPrompt', () => ({ + buildCowriterSystemPrompt: vi.fn(() => 'mock-sys'), +})) +vi.mock('../../src/providers/ModelProvider', () => ({ + ProviderFactory: { create: vi.fn(() => ({ stream: vi.fn(async function*() { yield 'x' }) })) }, +})) +vi.mock('../../src/utils/streamBridge', () => ({ streamToWebview: vi.fn(async () => {}) })) +vi.mock('../../src/utils/tokenCounter', () => ({ + calculateCost: vi.fn(() => ({ inputTokens: 0, outputTokens: 0, inputCostUSD: 0, outputCostUSD: 0, totalCostUSD: 0 })), +})) +vi.mock('../../src/commands/configureKeys', () => ({ + getApiKey: vi.fn(async () => 'sk-test'), setExtensionContext: vi.fn(), +})) +vi.mock('../../src/storage/PromptStore', () => ({ + PromptStore: { initialize: vi.fn(), getInstance: vi.fn(() => ({ savePrompt: vi.fn(async () => 'p-id') })) }, +})) + +import { EditorPanel } from '../../src/panels/EditorPanel' + +beforeEach(() => { vi.clearAllMocks(); mocks.html = ''; EditorPanel.currentPanel = undefined }) + +describe('EditorPanel', () => { + it('deve criar panel com HTML contendo CSP e nonce', () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.html).toContain('') + expect(mocks.html).toContain('Content-Security-Policy') + expect(mocks.html).toMatch(/nonce="[A-Za-z0-9+/=]+"/) + expect(mocks.html).toContain('id="root"') + }) + + it('deve ser singleton', () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.createWebviewPanel).toHaveBeenCalledOnce() + }) + + it('deve registrar listener de mensagens', () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.onDidReceiveMessage).toHaveBeenCalled() + }) + + it('deve criar panel com enableScripts e retainContextWhenHidden', () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + const opts = mocks.createWebviewPanel.mock.calls[0][3] + expect(opts.enableScripts).toBe(true) + expect(opts.retainContextWhenHidden).toBe(true) + }) + + it('deve limpar currentPanel ao fazer dispose', () => { + const panel = EditorPanel.createOrShow({ fsPath: '/ext' } as any) + expect(EditorPanel.currentPanel).toBeDefined() + panel.dispose() + expect(EditorPanel.currentPanel).toBeUndefined() + }) + + it('deve tratar SAVE_PROMPT e retornar PROMPT_SAVED', async () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + const handler = mocks.onDidReceiveMessage.mock.calls[0][0] + handler({ + type: 'SAVE_PROMPT', + payload: { name: 'T', systemPrompt: 's', userPromptTemplate: 't', variables: {}, model: 'm', params: { model: 'm', temperature: 1, maxTokens: 4096 }, tags: [] }, + }) + // Aguardar dynamic imports do handler async + await new Promise((r) => setTimeout(r, 100)) + expect(mocks.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ type: 'PROMPT_SAVED', payload: { promptId: 'p-id' } }), + ) + }) + + it('deve tratar CONFIGURE_KEYS', async () => { + EditorPanel.createOrShow({ fsPath: '/ext' } as any) + const handler = mocks.onDidReceiveMessage.mock.calls[0][0] + await handler({ type: 'CONFIGURE_KEYS', payload: undefined }) + expect(mocks.executeCommand).toHaveBeenCalledWith('promptforge.configureKeys') + }) +}) diff --git a/promptforge/tests/unit/extension.test.ts b/promptforge/tests/unit/extension.test.ts new file mode 100644 index 0000000..b4af187 --- /dev/null +++ b/promptforge/tests/unit/extension.test.ts @@ -0,0 +1,164 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockRegisterCommand = vi.fn() +const mockShowErrorMessage = vi.fn() +const mockSubscriptions: { push: typeof vi.fn }[] = [] + +vi.mock('vscode', () => ({ + commands: { registerCommand: (...args: unknown[]) => mockRegisterCommand(...args) }, + window: { showErrorMessage: (...args: unknown[]) => mockShowErrorMessage(...args) }, + workspace: { workspaceFolders: [{ uri: { fsPath: '/tmp/test-ws' } }] }, + Uri: { joinPath: (...parts: unknown[]) => ({ fsPath: String(parts.join('/')) }) }, +})) + +vi.mock('../../src/panels/EditorPanel', () => ({ + EditorPanel: { createOrShow: vi.fn() }, +})) +vi.mock('../../src/panels/TestPanel', () => ({ + TestPanel: { createOrShow: vi.fn() }, +})) +vi.mock('../../src/panels/LibraryPanel', () => ({ + LibraryPanel: { createOrShow: vi.fn() }, +})) +vi.mock('../../src/commands/configureKeys', () => ({ + setExtensionContext: vi.fn(), + configureKeysCommand: vi.fn(), +})) +vi.mock('../../src/storage/PromptStore', () => ({ + PromptStore: { initialize: vi.fn() }, +})) +vi.mock('../../src/storage/ReplayStore', () => ({ + ReplayStore: { initialize: vi.fn() }, +})) + +import { activate, deactivate } from '../../src/extension' +import { EditorPanel } from '../../src/panels/EditorPanel' +import { TestPanel } from '../../src/panels/TestPanel' +import { LibraryPanel } from '../../src/panels/LibraryPanel' +import { setExtensionContext } from '../../src/commands/configureKeys' +import { PromptStore } from '../../src/storage/PromptStore' +import { ReplayStore } from '../../src/storage/ReplayStore' + +beforeEach(() => { + vi.clearAllMocks() + mockRegisterCommand.mockReturnValue({ dispose: vi.fn() }) +}) + +function createMockContext() { + const subs: unknown[] = [] + return { + extensionUri: { fsPath: '/ext' }, + subscriptions: subs, + secrets: { store: vi.fn(), get: vi.fn() }, + globalState: { get: vi.fn(), update: vi.fn() }, + workspaceState: { get: vi.fn(), update: vi.fn() }, + } as any +} + +describe('activate', () => { + it('deve registrar 4 comandos', () => { + const ctx = createMockContext() + activate(ctx) + expect(mockRegisterCommand).toHaveBeenCalledTimes(4) + }) + + it('deve registrar comando promptforge.newPrompt', () => { + const ctx = createMockContext() + activate(ctx) + expect(mockRegisterCommand).toHaveBeenCalledWith('promptforge.newPrompt', expect.any(Function)) + }) + + it('deve registrar comando promptforge.testPrompt', () => { + const ctx = createMockContext() + activate(ctx) + expect(mockRegisterCommand).toHaveBeenCalledWith( + 'promptforge.testPrompt', + expect.any(Function), + ) + }) + + it('deve registrar comando promptforge.openLibrary', () => { + const ctx = createMockContext() + activate(ctx) + expect(mockRegisterCommand).toHaveBeenCalledWith( + 'promptforge.openLibrary', + expect.any(Function), + ) + }) + + it('deve registrar comando promptforge.configureKeys', () => { + const ctx = createMockContext() + activate(ctx) + expect(mockRegisterCommand).toHaveBeenCalledWith( + 'promptforge.configureKeys', + expect.any(Function), + ) + }) + + it('deve inicializar stores e contexto', () => { + const ctx = createMockContext() + activate(ctx) + expect(setExtensionContext).toHaveBeenCalledWith(ctx) + expect(PromptStore.initialize).toHaveBeenCalledWith(ctx) + expect(ReplayStore.initialize).toHaveBeenCalledWith(ctx) + }) + + it('deve adicionar subscriptions ao contexto', () => { + const ctx = createMockContext() + activate(ctx) + expect(ctx.subscriptions.length).toBe(4) + }) + + it('deve exibir erro se inicializacao falhar', () => { + const ctx = createMockContext() + vi.mocked(PromptStore.initialize).mockImplementationOnce(() => { + throw new Error('Falha no storage') + }) + activate(ctx) + expect(mockShowErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Falha no storage'), + ) + // Nao registra comandos quando inicializacao falha + expect(mockRegisterCommand).not.toHaveBeenCalled() + }) + + it('comando newPrompt deve chamar EditorPanel.createOrShow', () => { + const ctx = createMockContext() + activate(ctx) + const newPromptCallback = mockRegisterCommand.mock.calls.find( + (c: unknown[]) => c[0] === 'promptforge.newPrompt', + )![1] as Function + newPromptCallback() + expect(EditorPanel.createOrShow).toHaveBeenCalledWith(ctx.extensionUri) + }) + + it('comando testPrompt deve chamar TestPanel.createOrShow', () => { + const ctx = createMockContext() + activate(ctx) + const callback = mockRegisterCommand.mock.calls.find( + (c: unknown[]) => c[0] === 'promptforge.testPrompt', + )![1] as Function + callback() + expect(TestPanel.createOrShow).toHaveBeenCalledWith(ctx.extensionUri) + }) + + it('comando openLibrary deve chamar LibraryPanel.createOrShow', () => { + const ctx = createMockContext() + activate(ctx) + const callback = mockRegisterCommand.mock.calls.find( + (c: unknown[]) => c[0] === 'promptforge.openLibrary', + )![1] as Function + callback() + expect(LibraryPanel.createOrShow).toHaveBeenCalledWith(ctx.extensionUri) + }) +}) + +describe('deactivate', () => { + it('deve existir como funcao', () => { + expect(typeof deactivate).toBe('function') + }) + + it('deve executar sem erro', () => { + expect(() => deactivate()).not.toThrow() + }) +}) diff --git a/promptforge/tests/unit/integration.test.ts b/promptforge/tests/unit/integration.test.ts new file mode 100644 index 0000000..216ee6b --- /dev/null +++ b/promptforge/tests/unit/integration.test.ts @@ -0,0 +1,247 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import * as fs from 'fs' +import * as path from 'path' +import * as os from 'os' + +/** + * Testes de integracao: validam o fluxo completo sem chamadas de API reais. + * Cobrem o pipeline: store -> variableParser -> tokenCounter -> snippetGenerator. + */ + +let tmpDir: string +const mockWorkspaceFolders = [{ uri: { fsPath: '' } }] + +vi.mock('vscode', () => ({ + workspace: { + get workspaceFolders() { + return mockWorkspaceFolders + }, + }, +})) + +import { PromptStore } from '../../src/storage/PromptStore' +import { ReplayStore } from '../../src/storage/ReplayStore' +import { parseVariables, resolveVariables } from '../../src/utils/variableParser' +import { calculateCost } from '../../src/utils/tokenCounter' +import { generateSnippet } from '../../src/utils/snippetGenerator' +import { getGuidelines } from '../../src/bestPractices/index' +import { buildCowriterSystemPrompt } from '../../src/bestPractices/cowriterSystemPrompt' +import { SemanticScorer } from '../../src/evaluators/SemanticScorer' + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pf-integ-')) + mockWorkspaceFolders[0].uri.fsPath = tmpDir + + const mockContext = { + secrets: { store: vi.fn(), get: vi.fn() }, + globalState: { get: vi.fn(), update: vi.fn() }, + workspaceState: { get: vi.fn(), update: vi.fn() }, + subscriptions: [], + } + PromptStore.initialize(mockContext as any) + ReplayStore.initialize(mockContext as any) +}) + +afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) +}) + +describe('Fluxo completo: criar prompt, testar, salvar replay', () => { + it('deve salvar prompt, depois capturar replay com dados do teste', async () => { + const promptStore = PromptStore.getInstance() + const replayStore = ReplayStore.getInstance() + + // 1. Salvar prompt + const promptId = await promptStore.savePrompt({ + name: 'Email Generator', + description: 'Gera emails de vendas', + systemPrompt: 'Voce e um copywriter. Gere emails de vendas para {{produto}}.', + userPromptTemplate: 'Crie um email para {{produto}} destinado a {{audiencia}}.', + variables: { produto: 'SaaS', audiencia: 'CTOs' }, + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + tags: ['email', 'vendas'], + }) + expect(promptId).toBeDefined() + + // 2. Verificar prompt salvo + const saved = await promptStore.getPrompt(promptId) + expect(saved!.name).toBe('Email Generator') + expect(saved!.versions).toHaveLength(1) + + // 3. Resolver variaveis + const resolved = resolveVariables(saved!.versions[0].userPromptTemplate, { + produto: 'CloudSync Pro', + audiencia: 'CTOs de startups', + }) + expect(resolved).toBe('Crie um email para CloudSync Pro destinado a CTOs de startups.') + expect(resolved).not.toContain('{{') + + // 4. Simular custo do teste + const cost = calculateCost('claude-opus-4-6', 500, 300) + expect(cost.totalCostUSD).toBeGreaterThan(0) + + // 5. Capturar replay + const replayId = await replayStore.captureReplay({ + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: saved!.versions[0].systemPrompt, + userInput: resolved, + originalOutput: 'Prezado CTO, gostaria de apresentar o CloudSync Pro...', + originalCostUSD: cost.totalCostUSD, + originalLatencyMs: 1200, + }) + + // 6. Verificar replay + const replays = await replayStore.loadReplays() + expect(replays).toHaveLength(1) + expect(replays[0].id).toBe(replayId) + expect(replays[0].originalOutput).toContain('CloudSync Pro') + }) +}) + +describe('Fluxo completo: guidelines -> co-writer system prompt -> snippet', () => { + it('deve gerar system prompt adaptado e snippet correto para Claude', () => { + const guidelines = getGuidelines('claude-opus-4-6') + expect(guidelines.preferredStructure).toBe('xml-tags') + + const systemPrompt = buildCowriterSystemPrompt( + 'claude-opus-4-6', + guidelines, + 'Gerar resumos de artigos', + 'Artigos cientificos de IA', + 'Pesquisadores', + 'Resumo automatico', + ) + expect(systemPrompt).toContain('claude-opus-4-6') + expect(systemPrompt).toContain('xml-tags') + expect(systemPrompt).toContain('Gerar resumos de artigos') + + const snippet = generateSnippet({ + language: 'typescript', + provider: 'anthropic', + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: 'Resuma artigos cientificos.', + userInput: 'Artigo sobre transformers...', + }) + expect(snippet).toContain("import Anthropic from '@anthropic-ai/sdk'") + expect(snippet).toContain('claude-opus-4-6') + expect(snippet).toContain('4096') + }) + + it('deve gerar system prompt adaptado para o-series', () => { + const guidelines = getGuidelines('o3') + expect(guidelines.preferredStructure).toBe('plain-instruction') + + const systemPrompt = buildCowriterSystemPrompt( + 'o3', + guidelines, + 'Resolver problemas matematicos', + 'Equacoes diferenciais', + 'Estudantes universitarios', + 'Tutoria', + ) + expect(systemPrompt).toContain('plain-instruction') + expect(systemPrompt).toContain('NUNCA instrua o-series') + + const snippet = generateSnippet({ + language: 'python', + provider: 'openai', + model: 'o3', + params: { model: 'o3', temperature: 1.0, maxTokens: 8192 }, + systemPrompt: 'Resolve equacoes.', + userInput: 'Resolva dy/dx = x^2', + }) + expect(snippet).toContain('from openai import OpenAI') + expect(snippet).toContain('o3') + }) +}) + +describe('Fluxo completo: variaveis + scoring', () => { + it('deve detectar variaveis, resolver e calcular score', () => { + const template = 'Gere um {{tipo}} sobre {{assunto:IA}} para {{audiencia}}' + const vars = parseVariables(template) + expect(vars).toHaveLength(3) + expect(vars[1].defaultValue).toBe('IA') + + const resolved = resolveVariables(template, { + tipo: 'artigo', + assunto: 'machine learning', + audiencia: 'desenvolvedores', + }) + expect(resolved).toBe( + 'Gere um artigo sobre machine learning para desenvolvedores', + ) + + const actual = 'Artigo sobre machine learning para desenvolvedores iniciantes' + const desired = 'Gere um artigo sobre machine learning para desenvolvedores' + const score = SemanticScorer.score(actual, desired) + expect(score).toBeGreaterThan(0.5) + }) +}) + +describe('Fluxo completo: versionamento e arquivamento', () => { + it('deve criar 16 versoes e arquivar excedente', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt({ + name: 'Iterative Prompt', + systemPrompt: 'v1', + userPromptTemplate: 'template', + variables: {}, + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + tags: ['iterative'], + }) + + for (let i = 2; i <= 16; i++) { + await store.savePrompt({ + promptId: id, + name: 'Iterative Prompt', + systemPrompt: `v${i}`, + userPromptTemplate: 'template', + variables: {}, + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + tags: ['iterative'], + }) + } + + const prompt = await store.getPrompt(id) + expect(prompt!.versions.length).toBeLessThanOrEqual(15) + + // Verificar que o arquivo mais recente e a ultima versao + const lastVersion = prompt!.versions[prompt!.versions.length - 1] + expect(lastVersion.systemPrompt).toBe('v16') + + // Verificar que archive existe + const archivePath = path.join(tmpDir, '.promptforge', 'archive', `${id}-archived.json`) + expect(fs.existsSync(archivePath)).toBe(true) + }) +}) + +describe('Fluxo completo: export snippet 9 combinacoes', () => { + const languages = ['typescript', 'python', 'curl'] as const + const providers = ['anthropic', 'openai', 'google'] as const + + for (const lang of languages) { + for (const prov of providers) { + it(`deve gerar snippet valido para ${lang}/${prov}`, () => { + const snippet = generateSnippet({ + language: lang, + provider: prov, + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: 'Voce e um assistente.', + userInput: 'Ola!', + }) + expect(snippet.length).toBeGreaterThan(50) + // Verificar que nenhuma variavel de template ficou sem substituir + expect(snippet).not.toContain('{{model}}') + expect(snippet).not.toContain('{{maxTokens}}') + expect(snippet).not.toContain('{{systemPrompt}}') + expect(snippet).not.toContain('{{userInput}}') + }) + } + } +}) diff --git a/promptforge/tests/unit/libraryPanel.test.ts b/promptforge/tests/unit/libraryPanel.test.ts new file mode 100644 index 0000000..75ecdab --- /dev/null +++ b/promptforge/tests/unit/libraryPanel.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mocks = vi.hoisted(() => ({ + postMessage: vi.fn(), + onDidReceiveMessage: vi.fn(), + onDidDispose: vi.fn(), + executeCommand: vi.fn(), + createWebviewPanel: vi.fn(), + html: '', +})) + +vi.mock('vscode', () => ({ + window: { + activeTextEditor: undefined, + createWebviewPanel: (...args: unknown[]) => { + mocks.createWebviewPanel(...args) + return { + webview: { + set html(val: string) { mocks.html = val }, + get html() { return mocks.html }, + postMessage: mocks.postMessage, + onDidReceiveMessage: mocks.onDidReceiveMessage, + asWebviewUri: (u: any) => u?.toString?.() ?? String(u), + cspSource: 'csp', + }, + onDidDispose: mocks.onDidDispose, + reveal: vi.fn(), + dispose: vi.fn(), + } + }, + }, + commands: { executeCommand: mocks.executeCommand }, + ViewColumn: { One: 1 }, + Uri: { joinPath: (...p: any[]) => ({ fsPath: p.map((x: any) => x?.fsPath ?? x).join('/'), toString: () => p.join('/') }) }, + workspace: { workspaceFolders: [{ uri: { fsPath: '/tmp' } }] }, +})) + +const mockPrompts = [{ id: 'p1', name: 'P1', versions: [], tags: ['a'] }] + +vi.mock('../../src/storage/PromptStore', () => ({ + PromptStore: { initialize: vi.fn(), getInstance: vi.fn(() => ({ + listPrompts: vi.fn(async () => mockPrompts), + savePrompt: vi.fn(async () => 'p-new'), + deletePrompt: vi.fn(async () => {}), + })) }, +})) +vi.mock('../../src/utils/snippetGenerator', () => ({ generateSnippet: vi.fn(() => 'code()') })) +vi.mock('../../src/commands/configureKeys', () => ({ setExtensionContext: vi.fn() })) + +import { LibraryPanel } from '../../src/panels/LibraryPanel' + +const tick = () => new Promise((r) => setTimeout(r, 100)) + +beforeEach(() => { vi.clearAllMocks(); mocks.html = ''; LibraryPanel.currentPanel = undefined }) + +function callHandler(msg: unknown) { + mocks.onDidReceiveMessage.mock.calls[0][0](msg) +} + +describe('LibraryPanel', () => { + it('deve criar panel com CSP', () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.html).toContain('Content-Security-Policy') + }) + + it('deve ser singleton', () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.createWebviewPanel).toHaveBeenCalledOnce() + }) + + it('deve tratar LOAD_LIBRARY', async () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'LOAD_LIBRARY', payload: undefined }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'LIBRARY_DATA' })) + }) + + it('deve tratar DELETE_PROMPT', async () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'DELETE_PROMPT', payload: { promptId: 'p1' } }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'LIBRARY_DATA' })) + }) + + it('deve tratar GENERATE_SNIPPET', async () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ + type: 'GENERATE_SNIPPET', + payload: { language: 'typescript', provider: 'anthropic', model: 'm', params: { model: 'm', temperature: 1, maxTokens: 4096 }, systemPrompt: 's', userInput: 'u' }, + }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'SNIPPET_RESULT' })) + }) + + it('deve tratar SAVE_PROMPT', async () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ + type: 'SAVE_PROMPT', + payload: { name: 'N', systemPrompt: 's', userPromptTemplate: 't', variables: {}, model: 'm', params: { model: 'm', temperature: 1, maxTokens: 4096 }, tags: [] }, + }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'PROMPT_SAVED' })) + }) + + it('deve tratar CONFIGURE_KEYS', async () => { + LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'CONFIGURE_KEYS', payload: undefined }) + await tick() + expect(mocks.executeCommand).toHaveBeenCalledWith('promptforge.configureKeys') + }) + + it('deve limpar currentPanel ao dispose', () => { + const p = LibraryPanel.createOrShow({ fsPath: '/ext' } as any) + p.dispose() + expect(LibraryPanel.currentPanel).toBeUndefined() + }) +}) diff --git a/promptforge/tests/unit/messageTypes.test.ts b/promptforge/tests/unit/messageTypes.test.ts new file mode 100644 index 0000000..f691339 --- /dev/null +++ b/promptforge/tests/unit/messageTypes.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest' +import { MODEL_CATALOG, DEFAULT_PARAMS } from '../../src/types/models' +import { SCHEMA_VERSION } from '../../src/types/prompts' + +describe('MODEL_CATALOG', () => { + it('deve ter 4 providers', () => { + expect(Object.keys(MODEL_CATALOG)).toHaveLength(4) + expect(Object.keys(MODEL_CATALOG)).toEqual( + expect.arrayContaining(['anthropic', 'openai', 'google', 'openrouter']), + ) + }) + + it('deve ter 3 modelos Anthropic', () => { + expect(MODEL_CATALOG.anthropic).toHaveLength(3) + }) + + it('deve ter 4 modelos OpenAI', () => { + expect(MODEL_CATALOG.openai).toHaveLength(4) + }) + + it('deve ter 3 modelos Google', () => { + expect(MODEL_CATALOG.google).toHaveLength(3) + }) + + it('deve ter 1 modelo OpenRouter', () => { + expect(MODEL_CATALOG.openrouter).toHaveLength(1) + }) + + it('todos os modelos devem ter id e label', () => { + for (const [provider, models] of Object.entries(MODEL_CATALOG)) { + for (const model of models) { + expect(model.id, `${provider}: id ausente`).toBeDefined() + expect(model.label, `${provider}: label ausente`).toBeDefined() + expect(model.tier, `${provider}: tier ausente`).toBeDefined() + expect(model.id.length).toBeGreaterThan(0) + expect(model.label.length).toBeGreaterThan(0) + } + } + }) + + it('todos os IDs devem ser unicos', () => { + const allIds = Object.values(MODEL_CATALOG) + .flat() + .map((m) => m.id) + const uniqueIds = new Set(allIds) + expect(uniqueIds.size).toBe(allIds.length) + }) +}) + +describe('DEFAULT_PARAMS', () => { + it('deve ter parametros para todos os modelos (exceto openrouter)', () => { + const catalogModels = Object.values(MODEL_CATALOG) + .flat() + .map((m) => m.id) + .filter((id) => !id.startsWith('openrouter')) + + for (const model of catalogModels) { + expect(DEFAULT_PARAMS[model], `DEFAULT_PARAMS ausente para ${model}`).toBeDefined() + } + }) + + it('todos os modelos devem ter temperature 1.0', () => { + for (const [model, params] of Object.entries(DEFAULT_PARAMS)) { + expect(params.temperature, `${model} temperature`).toBe(1.0) + } + }) + + it('todos os modelos devem ter maxTokens positivo', () => { + for (const [model, params] of Object.entries(DEFAULT_PARAMS)) { + expect(params.maxTokens, `${model} maxTokens`).toBeGreaterThan(0) + } + }) + + it('modelos Claude flagship devem ter reasoningEffort', () => { + expect(DEFAULT_PARAMS['claude-opus-4-6'].reasoningEffort).toBeDefined() + expect(DEFAULT_PARAMS['claude-sonnet-4-6'].reasoningEffort).toBeDefined() + }) + + it('claude-haiku nao deve ter reasoningEffort', () => { + expect(DEFAULT_PARAMS['claude-haiku-4-5'].reasoningEffort).toBeUndefined() + }) + + it('gemini-3.1-pro deve ter thinkingLevel', () => { + expect(DEFAULT_PARAMS['gemini-3.1-pro-preview'].thinkingLevel).toBe('medium') + }) +}) + +describe('SCHEMA_VERSION', () => { + it('deve ser um inteiro positivo', () => { + expect(SCHEMA_VERSION).toBeGreaterThan(0) + expect(Number.isInteger(SCHEMA_VERSION)).toBe(true) + }) + + it('deve ser 1 para a versao inicial', () => { + expect(SCHEMA_VERSION).toBe(1) + }) +}) diff --git a/promptforge/tests/unit/openai.real.test.ts b/promptforge/tests/unit/openai.real.test.ts new file mode 100644 index 0000000..7b49b38 --- /dev/null +++ b/promptforge/tests/unit/openai.real.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from 'vitest' + +/** + * Teste de integracao REAL com a API da OpenAI. + * Valida que o SDK OpenAI funciona com key real. + * Usa Chat Completions API (compativel com todos os modelos atuais). + */ + +const OPENAI_API_KEY = process.env.OPENAI_API_KEY + +// Skip se nao tiver key OU se estiver em ambiente sem rede (CI/sandbox) +const skip = !OPENAI_API_KEY || process.env.CI === 'true' || process.env.NO_NETWORK === 'true' + +describe.skipIf(skip)('OpenAI Real API Integration', () => { + it('deve completar uma chamada real via Chat Completions', async () => { + const OpenAI = (await import('openai')).default + const client = new OpenAI({ apiKey: OPENAI_API_KEY }) + + const response = await client.chat.completions.create({ + model: 'gpt-4o-mini', + max_tokens: 50, + messages: [ + { role: 'system', content: 'Responda em uma frase curta.' }, + { role: 'user', content: 'Qual e a capital do Brasil?' }, + ], + }) + + const text = response.choices[0]?.message?.content ?? '' + expect(text.length).toBeGreaterThan(0) + console.log('[OpenAI Real] Response:', text) + console.log('[OpenAI Real] Tokens — input:', response.usage?.prompt_tokens, 'output:', response.usage?.completion_tokens) + }, 60000) + + it('deve fazer streaming real via Chat Completions', async () => { + const OpenAI = (await import('openai')).default + const client = new OpenAI({ apiKey: OPENAI_API_KEY }) + + const stream = await client.chat.completions.create({ + model: 'gpt-4o-mini', + max_tokens: 30, + messages: [ + { role: 'system', content: 'Responda com uma palavra.' }, + { role: 'user', content: 'Diga apenas: OK' }, + ], + stream: true, + }) + + let fullText = '' + let chunkCount = 0 + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta?.content + if (delta) { + fullText += delta + chunkCount++ + } + } + + expect(fullText.length).toBeGreaterThan(0) + expect(chunkCount).toBeGreaterThan(0) + console.log('[OpenAI Stream] Chunks:', chunkCount, 'Text:', fullText) + }, 60000) + + it('deve calcular custo real baseado na resposta', async () => { + const { calculateCost } = await import('../../src/utils/tokenCounter') + const cost = calculateCost('gpt-5.4-mini', 100, 50) + expect(cost.totalCostUSD).toBeGreaterThan(0) + expect(cost.totalCostUSD).toBeLessThan(0.01) + console.log('[Cost] GPT-5.4-mini 100in/50out: $' + cost.totalCostUSD.toFixed(6)) + }) +}) diff --git a/promptforge/tests/unit/panelHtml.test.ts b/promptforge/tests/unit/panelHtml.test.ts new file mode 100644 index 0000000..722bb1b --- /dev/null +++ b/promptforge/tests/unit/panelHtml.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect, vi } from 'vitest' + +// Mock minimo de vscode e crypto +vi.mock('vscode', () => ({ + Uri: { + joinPath: (...parts: string[]) => ({ fsPath: parts.join('/'), toString: () => parts.join('/') }), + }, + window: {}, +})) + +// Testar a logica de geracao de HTML diretamente via regex +// Nao podemos instanciar BasePanel (abstrata), entao testamos a estrutura do HTML + +describe('BasePanel HTML structure', () => { + // Simular a geracao de HTML que BasePanel produz + function generateHtml(nonce: string, scriptUri: string, styleUri: string, cspSource: string) { + const csp = [ + `default-src 'none'`, + `style-src ${cspSource} 'unsafe-inline'`, + `script-src 'nonce-${nonce}'`, + `font-src ${cspSource}`, + `img-src ${cspSource} data:`, + ].join('; ') + + return ` + + + + + + + PromptForge + + +
+ + +` + } + + it('deve conter DOCTYPE html', () => { + const html = generateHtml('abc123', '/script.js', '/style.css', 'webview-csp') + expect(html).toContain('') + }) + + it('deve conter meta CSP', () => { + const html = generateHtml('abc123', '/script.js', '/style.css', 'webview-csp') + expect(html).toContain('Content-Security-Policy') + }) + + it('deve conter nonce no CSP e no script', () => { + const nonce = 'test-nonce-12345' + const html = generateHtml(nonce, '/script.js', '/style.css', 'webview-csp') + expect(html).toContain(`nonce-${nonce}`) + expect(html).toContain(`nonce="${nonce}"`) + }) + + it('deve conter link stylesheet', () => { + const html = generateHtml('n', '/s.js', '/main.css', 'csp') + expect(html).toContain('') + }) + + it('deve conter div root para React', () => { + const html = generateHtml('n', '/s.js', '/c.css', 'csp') + expect(html).toContain('
') + }) + + it('deve conter script tag com src', () => { + const html = generateHtml('n', '/dist/webview/main.js', '/c.css', 'csp') + expect(html).toContain('src="/dist/webview/main.js"') + }) + + it('CSP deve bloquear default-src', () => { + const html = generateHtml('n', '/s.js', '/c.css', 'csp') + expect(html).toContain("default-src 'none'") + }) + + it('CSP deve permitir imagens com data:', () => { + const html = generateHtml('n', '/s.js', '/c.css', 'csp') + expect(html).toContain('img-src csp data:') + }) + + it('deve ter lang pt-BR', () => { + const html = generateHtml('n', '/s.js', '/c.css', 'csp') + expect(html).toContain('lang="pt-BR"') + }) + + it('deve ter title PromptForge', () => { + const html = generateHtml('n', '/s.js', '/c.css', 'csp') + expect(html).toContain('PromptForge') + }) +}) + +describe('Nonce generation', () => { + it('deve gerar nonces diferentes a cada chamada', () => { + const crypto = require('crypto') + const nonces = new Set() + for (let i = 0; i < 100; i++) { + nonces.add(crypto.randomBytes(16).toString('base64')) + } + expect(nonces.size).toBe(100) + }) + + it('nonce deve ter pelo menos 16 caracteres', () => { + const crypto = require('crypto') + const nonce = crypto.randomBytes(16).toString('base64') + expect(nonce.length).toBeGreaterThanOrEqual(16) + }) +}) diff --git a/promptforge/tests/unit/promptStore.extended.test.ts b/promptforge/tests/unit/promptStore.extended.test.ts new file mode 100644 index 0000000..1a0f6bb --- /dev/null +++ b/promptforge/tests/unit/promptStore.extended.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import * as fs from 'fs' +import * as path from 'path' +import * as os from 'os' + +let tmpDir: string +const mockWorkspaceFolders = [{ uri: { fsPath: '' } }] + +vi.mock('vscode', () => ({ + workspace: { + get workspaceFolders() { + return mockWorkspaceFolders + }, + }, +})) + +import { PromptStore } from '../../src/storage/PromptStore' + +const payload = { + name: 'P', systemPrompt: 's', userPromptTemplate: 't', + variables: {}, model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + tags: [], +} + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pf-ext-')) + mockWorkspaceFolders[0].uri.fsPath = tmpDir + PromptStore.initialize({ secrets: { store: vi.fn(), get: vi.fn() }, globalState: { get: vi.fn(), update: vi.fn() }, workspaceState: { get: vi.fn(), update: vi.fn() }, subscriptions: [] } as any) +}) + +afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) +}) + +describe('PromptStore - edge cases avancados', () => { + it('deve gerar IDs unicos para prompts diferentes', async () => { + const store = PromptStore.getInstance() + const ids = new Set() + for (let i = 0; i < 10; i++) { + ids.add(await store.savePrompt({ ...payload, name: `P${i}` })) + } + expect(ids.size).toBe(10) + }) + + it('deve manter updatedAt atualizado ao adicionar versao', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + const p1 = await store.getPrompt(id) + const firstUpdated = p1!.updatedAt + + await new Promise((r) => setTimeout(r, 10)) + await store.savePrompt({ ...payload, promptId: id, systemPrompt: 'v2' }) + const p2 = await store.getPrompt(id) + + expect(new Date(p2!.updatedAt).getTime()).toBeGreaterThan(new Date(firstUpdated).getTime()) + }) + + it('deve preservar createdAt ao atualizar', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + const p1 = await store.getPrompt(id) + + await store.savePrompt({ ...payload, promptId: id }) + const p2 = await store.getPrompt(id) + + expect(p2!.createdAt).toBe(p1!.createdAt) + }) + + it('deve lidar com deletar prompt inexistente sem erro', async () => { + const store = PromptStore.getInstance() + await expect(store.deletePrompt('id-que-nao-existe')).resolves.not.toThrow() + }) + + it('deve gerar versionNumber incremental correto', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + for (let i = 0; i < 5; i++) { + await store.savePrompt({ ...payload, promptId: id }) + } + const p = await store.getPrompt(id) + expect(p!.versions.map((v) => v.versionNumber)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']) + }) + + it('deve manter dados consistentes apos arquivamento', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + for (let i = 0; i < 16; i++) { + await store.savePrompt({ ...payload, promptId: id, systemPrompt: `v${i + 2}` }) + } + const p = await store.getPrompt(id) + // Todas as versoes restantes devem ter versionNumber definido + for (const v of p!.versions) { + expect(v.versionNumber).toBeDefined() + expect(v.systemPrompt).toBeDefined() + } + // Ultima versao deve ser v17 + expect(p!.versions[p!.versions.length - 1].systemPrompt).toBe('v17') + }) + + it('deve inicializar cada versao com testHistory vazio e avgCostUSD 0', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + const p = await store.getPrompt(id) + expect(p!.versions[0].testHistory).toEqual([]) + expect(p!.versions[0].avgCostUSD).toBe(0) + expect(p!.versions[0].totalRuns).toBe(0) + }) + + it('deve inicializar versao com status draft', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(payload) + const p = await store.getPrompt(id) + expect(p!.versions[0].status).toBe('draft') + }) +}) diff --git a/promptforge/tests/unit/promptStore.test.ts b/promptforge/tests/unit/promptStore.test.ts new file mode 100644 index 0000000..c23d490 --- /dev/null +++ b/promptforge/tests/unit/promptStore.test.ts @@ -0,0 +1,193 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import * as fs from 'fs' +import * as path from 'path' +import * as os from 'os' + +let tmpDir: string +const mockWorkspaceFolders = [{ uri: { fsPath: '' } }] + +vi.mock('vscode', () => ({ + workspace: { + get workspaceFolders() { + return mockWorkspaceFolders + }, + }, +})) + +import { PromptStore } from '../../src/storage/PromptStore' +import { SCHEMA_VERSION } from '../../src/types/prompts' + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pf-test-')) + mockWorkspaceFolders[0].uri.fsPath = tmpDir + + const mockContext = { + secrets: { store: vi.fn(), get: vi.fn() }, + globalState: { get: vi.fn(), update: vi.fn() }, + workspaceState: { get: vi.fn(), update: vi.fn() }, + subscriptions: [], + } + PromptStore.initialize(mockContext as any) +}) + +afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) +}) + +const basePayload = { + name: 'Test Prompt', + description: 'Um prompt de teste', + systemPrompt: 'Voce e um assistente.', + userPromptTemplate: 'Fale sobre {{tema}}', + variables: { tema: 'IA' }, + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + tags: ['teste', 'demo'], +} + +describe('PromptStore', () => { + it('deve criar novo prompt com ID e arquivo JSON', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + + expect(id).toBeDefined() + expect(typeof id).toBe('string') + expect(id.length).toBeGreaterThan(0) + + const filePath = path.join(tmpDir, '.promptforge', 'prompts', `${id}.json`) + expect(fs.existsSync(filePath)).toBe(true) + + const saved = JSON.parse(fs.readFileSync(filePath, 'utf-8')) + expect(saved.name).toBe('Test Prompt') + expect(saved.schemaVersion).toBe(SCHEMA_VERSION) + expect(saved.versions).toHaveLength(1) + expect(saved.versions[0].versionNumber).toBe('v1') + expect(saved.versions[0].status).toBe('draft') + }) + + it('deve atualizar prompt existente com nova versao', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + + await store.savePrompt({ + ...basePayload, + promptId: id, + systemPrompt: 'System prompt atualizado', + }) + + const prompt = await store.getPrompt(id) + expect(prompt).not.toBeNull() + expect(prompt!.versions).toHaveLength(2) + expect(prompt!.versions[1].versionNumber).toBe('v2') + expect(prompt!.versions[1].systemPrompt).toBe('System prompt atualizado') + }) + + it('deve listar todos os prompts salvos', async () => { + const store = PromptStore.getInstance() + await store.savePrompt(basePayload) + await store.savePrompt({ ...basePayload, name: 'Prompt 2' }) + + const prompts = await store.listPrompts() + expect(prompts).toHaveLength(2) + expect(prompts.map((p) => p.name)).toContain('Test Prompt') + expect(prompts.map((p) => p.name)).toContain('Prompt 2') + }) + + it('deve deletar prompt por ID', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + + await store.deletePrompt(id) + + const prompt = await store.getPrompt(id) + expect(prompt).toBeNull() + }) + + it('deve retornar null para prompt inexistente', async () => { + const store = PromptStore.getInstance() + const prompt = await store.getPrompt('id-inexistente') + expect(prompt).toBeNull() + }) + + it('deve retornar lista vazia quando nao ha prompts', async () => { + const store = PromptStore.getInstance() + const prompts = await store.listPrompts() + expect(prompts).toEqual([]) + }) + + it('deve criar diretorio .promptforge/prompts automaticamente', async () => { + const store = PromptStore.getInstance() + const promptsDir = path.join(tmpDir, '.promptforge', 'prompts') + expect(fs.existsSync(promptsDir)).toBe(false) + + await store.savePrompt(basePayload) + expect(fs.existsSync(promptsDir)).toBe(true) + }) + + it('deve arquivar versoes quando exceder 15', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + + for (let i = 0; i < 15; i++) { + await store.savePrompt({ + ...basePayload, + promptId: id, + systemPrompt: `Versao ${i + 2}`, + }) + } + + const prompt = await store.getPrompt(id) + expect(prompt!.versions.length).toBeLessThanOrEqual(15) + + const archiveDir = path.join(tmpDir, '.promptforge', 'archive') + const archiveFile = path.join(archiveDir, `${id}-archived.json`) + expect(fs.existsSync(archiveFile)).toBe(true) + + const archived = JSON.parse(fs.readFileSync(archiveFile, 'utf-8')) + expect(archived.length).toBeGreaterThan(0) + }) + + it('deve incluir schemaVersion em todo prompt salvo', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + + const prompt = await store.getPrompt(id) + expect(prompt!.schemaVersion).toBe(SCHEMA_VERSION) + expect(typeof prompt!.schemaVersion).toBe('number') + }) + + it('deve ignorar JSON corrompido na listagem', async () => { + const store = PromptStore.getInstance() + await store.savePrompt(basePayload) + + const dir = path.join(tmpDir, '.promptforge', 'prompts') + fs.writeFileSync(path.join(dir, 'corrupted.json'), '{invalid json', 'utf-8') + + const prompts = await store.listPrompts() + expect(prompts).toHaveLength(1) + }) + + it('deve lancar erro para getPrompt com JSON corrompido', async () => { + const store = PromptStore.getInstance() + const dir = path.join(tmpDir, '.promptforge', 'prompts') + fs.mkdirSync(dir, { recursive: true }) + fs.writeFileSync(path.join(dir, 'bad-id.json'), 'not-json', 'utf-8') + + await expect(store.getPrompt('bad-id')).rejects.toThrow('corrompido') + }) + + it('deve preservar tags e descricao ao atualizar', async () => { + const store = PromptStore.getInstance() + const id = await store.savePrompt(basePayload) + await store.savePrompt({ + ...basePayload, + promptId: id, + tags: ['novo-tag'], + description: 'Nova descricao', + }) + + const prompt = await store.getPrompt(id) + expect(prompt!.tags).toEqual(['novo-tag']) + expect(prompt!.description).toBe('Nova descricao') + }) +}) diff --git a/promptforge/tests/unit/providerDefaults.test.ts b/promptforge/tests/unit/providerDefaults.test.ts new file mode 100644 index 0000000..ff93152 --- /dev/null +++ b/promptforge/tests/unit/providerDefaults.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, vi } from 'vitest' + +vi.mock('@anthropic-ai/sdk', () => ({ default: class { constructor() {}; messages = { create: vi.fn(), stream: vi.fn() } } })) +vi.mock('openai', () => ({ default: class { constructor() {}; responses = { create: vi.fn() }; chat = { completions: { create: vi.fn() } } } })) +vi.mock('@google/generative-ai', () => ({ + GoogleGenerativeAI: class { constructor() {}; getGenerativeModel() { return { generateContent: vi.fn(), generateContentStream: vi.fn() } } }, +})) + +import { AnthropicProvider } from '../../src/providers/AnthropicProvider' +import { OpenAIProvider } from '../../src/providers/OpenAIProvider' +import { GoogleProvider } from '../../src/providers/GoogleProvider' +import { OpenRouterProvider } from '../../src/providers/OpenRouterProvider' + +describe('Provider getDefaultParams()', () => { + it('Claude Opus deve ter maxTokens 4096 e reasoningEffort medium', () => { + const p = new AnthropicProvider('claude-opus-4-6', 'k') + const params = p.getDefaultParams() + expect(params.maxTokens).toBe(4096) + expect(params.temperature).toBe(1.0) + }) + + it('Claude Haiku deve ter maxTokens 2048', () => { + const p = new AnthropicProvider('claude-haiku-4-5', 'k') + expect(p.getDefaultParams().maxTokens).toBe(2048) + }) + + it('o3 deve ter maxTokens 8192', () => { + const p = new OpenAIProvider('o3', 'k') + expect(p.getDefaultParams().maxTokens).toBe(8192) + }) + + it('o4-mini deve ter maxTokens 8192', () => { + const p = new OpenAIProvider('o4-mini', 'k') + expect(p.getDefaultParams().maxTokens).toBe(8192) + }) + + it('GPT-5.4 deve ter maxTokens 4096', () => { + const p = new OpenAIProvider('gpt-5.4', 'k') + expect(p.getDefaultParams().maxTokens).toBe(4096) + }) + + it('Gemini 3.1 Pro deve ter maxTokens 8192', () => { + const p = new GoogleProvider('gemini-3.1-pro-preview', 'k') + expect(p.getDefaultParams().maxTokens).toBe(8192) + }) + + it('Gemini 2.5 Flash deve ter maxTokens 4096', () => { + const p = new GoogleProvider('gemini-2.5-flash', 'k') + expect(p.getDefaultParams().maxTokens).toBe(4096) + }) + + it('OpenRouter deve ter defaults razoaveis', () => { + const p = new OpenRouterProvider('openrouter/auto', 'k') + const params = p.getDefaultParams() + expect(params.model).toBe('openrouter/auto') + expect(params.temperature).toBe(1.0) + expect(params.maxTokens).toBeGreaterThan(0) + }) +}) + +describe('Provider estimateCost()', () => { + it('Anthropic deve retornar custo positivo', () => { + const p = new AnthropicProvider('claude-opus-4-6', 'k') + const cost = p.estimateCost(1000, 500) + expect(cost.totalCostUSD).toBeGreaterThan(0) + expect(cost.inputTokens).toBe(1000) + }) + + it('OpenAI deve retornar custo positivo', () => { + const p = new OpenAIProvider('gpt-5.4', 'k') + const cost = p.estimateCost(1000, 500) + expect(cost.totalCostUSD).toBeGreaterThan(0) + }) + + it('Google deve retornar custo positivo', () => { + const p = new GoogleProvider('gemini-3.1-pro-preview', 'k') + const cost = p.estimateCost(1000, 500) + expect(cost.totalCostUSD).toBeGreaterThan(0) + }) + + it('OpenRouter deve retornar custo com fallback', () => { + const p = new OpenRouterProvider('openrouter/auto', 'k') + const cost = p.estimateCost(1000, 500) + expect(cost.totalCostUSD).toBeGreaterThan(0) + }) +}) + +describe('Provider getBestPractices()', () => { + it('Anthropic Haiku deve retornar mesmas guidelines que Opus', () => { + const opus = new AnthropicProvider('claude-opus-4-6', 'k') + const haiku = new AnthropicProvider('claude-haiku-4-5', 'k') + expect(opus.getBestPractices().preferredStructure).toBe(haiku.getBestPractices().preferredStructure) + }) + + it('OpenAI GPT e o-series devem ter guidelines diferentes', () => { + const gpt = new OpenAIProvider('gpt-5.4', 'k') + const o3 = new OpenAIProvider('o3', 'k') + expect(gpt.getBestPractices().preferredStructure).not.toBe(o3.getBestPractices().preferredStructure) + }) + + it('Google Gemini 3 e 2.5 devem ter reasoningControl diferentes', () => { + const g3 = new GoogleProvider('gemini-3.1-pro-preview', 'k') + const g25 = new GoogleProvider('gemini-2.5-flash', 'k') + expect(g3.getBestPractices().reasoningControl).not.toBe(g25.getBestPractices().reasoningControl) + }) +}) diff --git a/promptforge/tests/unit/providers.test.ts b/promptforge/tests/unit/providers.test.ts new file mode 100644 index 0000000..dfe48bf --- /dev/null +++ b/promptforge/tests/unit/providers.test.ts @@ -0,0 +1,151 @@ +import { describe, it, expect, vi } from 'vitest' +import { DEFAULT_PARAMS } from '../../src/types/models' + +// Precisamos mockar os providers pois ProviderFactory usa require() dinamico. +// Em vez de testar via factory, testamos diretamente cada provider. +vi.mock('@anthropic-ai/sdk', () => ({ + default: class MockAnthropic { + constructor() {} + messages = { create: vi.fn(), stream: vi.fn() } + }, +})) + +vi.mock('openai', () => ({ + default: class MockOpenAI { + constructor() {} + responses = { create: vi.fn() } + chat = { completions: { create: vi.fn() } } + }, +})) + +vi.mock('@google/generative-ai', () => ({ + GoogleGenerativeAI: class MockGenAI { + constructor() {} + getGenerativeModel() { + return { + generateContent: vi.fn(), + generateContentStream: vi.fn(), + } + } + }, +})) + +import { AnthropicProvider } from '../../src/providers/AnthropicProvider' +import { OpenAIProvider } from '../../src/providers/OpenAIProvider' +import { GoogleProvider } from '../../src/providers/GoogleProvider' +import { OpenRouterProvider } from '../../src/providers/OpenRouterProvider' + +describe('AnthropicProvider', () => { + it('deve implementar interface ModelProvider', () => { + const provider = new AnthropicProvider('claude-opus-4-6', 'sk-test') + expect(typeof provider.complete).toBe('function') + expect(typeof provider.stream).toBe('function') + expect(typeof provider.estimateCost).toBe('function') + expect(typeof provider.getBestPractices).toBe('function') + expect(typeof provider.getDefaultParams).toBe('function') + }) + + it('deve retornar guidelines Claude', () => { + const provider = new AnthropicProvider('claude-opus-4-6', 'sk-test') + const practices = provider.getBestPractices() + expect(practices.preferredStructure).toBe('xml-tags') + }) + + it('deve retornar parametros padrao corretos', () => { + const provider = new AnthropicProvider('claude-opus-4-6', 'sk-test') + const params = provider.getDefaultParams() + expect(params.model).toBe('claude-opus-4-6') + expect(params.temperature).toBe(1.0) + expect(params.maxTokens).toBe(4096) + }) + + it('deve estimar custo', () => { + const provider = new AnthropicProvider('claude-opus-4-6', 'sk-test') + const cost = provider.estimateCost(1000, 500) + expect(cost.totalCostUSD).toBeGreaterThan(0) + expect(cost.inputTokens).toBe(1000) + expect(cost.outputTokens).toBe(500) + }) +}) + +describe('OpenAIProvider', () => { + it('deve implementar interface ModelProvider', () => { + const provider = new OpenAIProvider('gpt-5.4', 'sk-test') + expect(typeof provider.complete).toBe('function') + expect(typeof provider.stream).toBe('function') + }) + + it('deve retornar guidelines GPT-5 para gpt-5.4', () => { + const provider = new OpenAIProvider('gpt-5.4', 'sk-test') + expect(provider.getBestPractices().preferredStructure).toBe('markdown-headers') + }) + + it('deve retornar guidelines o-series para o3', () => { + const provider = new OpenAIProvider('o3', 'sk-test') + expect(provider.getBestPractices().preferredStructure).toBe('plain-instruction') + expect(provider.getBestPractices().maxTemperature).toBe(1.0) + }) + + it('deve retornar guidelines o-series para o4-mini', () => { + const provider = new OpenAIProvider('o4-mini', 'sk-test') + expect(provider.getBestPractices().preferredStructure).toBe('plain-instruction') + }) + + it('deve retornar parametros padrao corretos para o3', () => { + const provider = new OpenAIProvider('o3', 'sk-test') + const params = provider.getDefaultParams() + expect(params.maxTokens).toBe(8192) + }) +}) + +describe('GoogleProvider', () => { + it('deve implementar interface ModelProvider', () => { + const provider = new GoogleProvider('gemini-3.1-pro-preview', 'key-test') + expect(typeof provider.complete).toBe('function') + expect(typeof provider.stream).toBe('function') + }) + + it('deve retornar guidelines Gemini 3 para gemini-3.1-*', () => { + const provider = new GoogleProvider('gemini-3.1-pro-preview', 'key-test') + expect(provider.getBestPractices().preferredStructure).toBe('declarative') + expect(provider.getBestPractices().temperatureNote).toBeDefined() + }) + + it('deve retornar guidelines Gemini 2.5 para gemini-2.5-*', () => { + const provider = new GoogleProvider('gemini-2.5-flash', 'key-test') + expect(provider.getBestPractices().reasoningControl).toContain('thinking-budget') + }) +}) + +describe('OpenRouterProvider', () => { + it('deve implementar interface ModelProvider', () => { + const provider = new OpenRouterProvider('openrouter/auto', 'sk-test') + expect(typeof provider.complete).toBe('function') + expect(typeof provider.stream).toBe('function') + }) + + it('deve retornar guidelines GPT-5 (fallback)', () => { + const provider = new OpenRouterProvider('openrouter/auto', 'sk-test') + expect(provider.getBestPractices().preferredStructure).toBe('markdown-headers') + }) +}) + +describe('DEFAULT_PARAMS', () => { + it('deve ter parametros padrao para todos os modelos do catalogo', () => { + const expectedModels = [ + 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5', + 'gpt-5.4', 'gpt-5.4-mini', 'o3', 'o4-mini', + 'gemini-3.1-pro-preview', 'gemini-2.5-flash', 'gemini-3.1-flash-lite-preview', + ] + for (const model of expectedModels) { + expect(DEFAULT_PARAMS[model]).toBeDefined() + expect(DEFAULT_PARAMS[model].temperature).toBe(1.0) + expect(DEFAULT_PARAMS[model].maxTokens).toBeGreaterThan(0) + } + }) + + it('o-series deve ter maxTokens 8192', () => { + expect(DEFAULT_PARAMS['o3'].maxTokens).toBe(8192) + expect(DEFAULT_PARAMS['o4-mini'].maxTokens).toBe(8192) + }) +}) diff --git a/promptforge/tests/unit/replayStore.test.ts b/promptforge/tests/unit/replayStore.test.ts new file mode 100644 index 0000000..d7bb00a --- /dev/null +++ b/promptforge/tests/unit/replayStore.test.ts @@ -0,0 +1,140 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import * as fs from 'fs' +import * as path from 'path' +import * as os from 'os' + +let tmpDir: string +const mockWorkspaceFolders = [{ uri: { fsPath: '' } }] + +vi.mock('vscode', () => ({ + workspace: { + get workspaceFolders() { + return mockWorkspaceFolders + }, + }, +})) + +import { ReplayStore } from '../../src/storage/ReplayStore' + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pf-replay-')) + mockWorkspaceFolders[0].uri.fsPath = tmpDir + + const mockContext = { + secrets: { store: vi.fn(), get: vi.fn() }, + globalState: { get: vi.fn(), update: vi.fn() }, + subscriptions: [], + } + ReplayStore.initialize(mockContext as any) +}) + +afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) +}) + +const basePayload = { + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: 'Voce e um assistente.', + userInput: 'Explique IA', + originalOutput: 'IA e inteligencia artificial...', + originalCostUSD: 0.0023, + originalLatencyMs: 1847, +} + +describe('ReplayStore', () => { + it('deve capturar replay e gravar arquivo JSON', async () => { + const store = ReplayStore.getInstance() + const id = await store.captureReplay(basePayload) + + expect(id).toBeDefined() + const filePath = path.join(tmpDir, '.promptforge', 'replays', `${id}.json`) + expect(fs.existsSync(filePath)).toBe(true) + + const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')) + expect(data.model).toBe('claude-opus-4-6') + expect(data.originalOutput).toBe('IA e inteligencia artificial...') + expect(data.capturedAt).toBeDefined() + }) + + it('deve listar replays ordenados por data (mais recente primeiro)', async () => { + const store = ReplayStore.getInstance() + await store.captureReplay({ ...basePayload, userInput: 'Primeiro' }) + await new Promise((r) => setTimeout(r, 10)) + await store.captureReplay({ ...basePayload, userInput: 'Segundo' }) + + const replays = await store.loadReplays() + expect(replays).toHaveLength(2) + expect(new Date(replays[0].capturedAt).getTime()).toBeGreaterThanOrEqual( + new Date(replays[1].capturedAt).getTime(), + ) + }) + + it('deve carregar replay por ID', async () => { + const store = ReplayStore.getInstance() + const id = await store.captureReplay(basePayload) + + const replay = await store.getReplay(id) + expect(replay).not.toBeNull() + expect(replay!.id).toBe(id) + expect(replay!.model).toBe('claude-opus-4-6') + }) + + it('deve retornar null para replay inexistente', async () => { + const store = ReplayStore.getInstance() + const replay = await store.getReplay('id-inexistente') + expect(replay).toBeNull() + }) + + it('deve capturar replay com label', async () => { + const store = ReplayStore.getInstance() + const id = await store.captureReplay({ ...basePayload, label: 'Melhor resultado' }) + + const replay = await store.getReplay(id) + expect(replay!.label).toBe('Melhor resultado') + }) + + it('deve capturar replay sem label', async () => { + const store = ReplayStore.getInstance() + const id = await store.captureReplay(basePayload) + + const replay = await store.getReplay(id) + expect(replay!.label).toBeUndefined() + }) + + it('deve retornar lista vazia quando nao ha replays', async () => { + const store = ReplayStore.getInstance() + const replays = await store.loadReplays() + expect(replays).toEqual([]) + }) + + it('deve ignorar arquivos corrompidos ao listar', async () => { + const store = ReplayStore.getInstance() + await store.captureReplay(basePayload) + + const dir = path.join(tmpDir, '.promptforge', 'replays') + fs.writeFileSync(path.join(dir, 'corrupted.json'), '{bad', 'utf-8') + + const replays = await store.loadReplays() + expect(replays).toHaveLength(1) + }) + + it('deve lancar erro para getReplay com JSON corrompido', async () => { + const store = ReplayStore.getInstance() + const dir = path.join(tmpDir, '.promptforge', 'replays') + fs.mkdirSync(dir, { recursive: true }) + fs.writeFileSync(path.join(dir, 'bad.json'), 'corrupted', 'utf-8') + + await expect(store.getReplay('bad')).rejects.toThrow('corrompido') + }) + + it('deve preservar campos imutaveis do replay', async () => { + const store = ReplayStore.getInstance() + const id = await store.captureReplay(basePayload) + + const replay = await store.getReplay(id) + expect(replay!.originalCostUSD).toBe(0.0023) + expect(replay!.originalLatencyMs).toBe(1847) + expect(replay!.systemPrompt).toBe('Voce e um assistente.') + }) +}) diff --git a/promptforge/tests/unit/semanticScorer.test.ts b/promptforge/tests/unit/semanticScorer.test.ts new file mode 100644 index 0000000..e2cf115 --- /dev/null +++ b/promptforge/tests/unit/semanticScorer.test.ts @@ -0,0 +1,77 @@ +import { describe, it, expect } from 'vitest' +import { SemanticScorer } from '../../src/evaluators/SemanticScorer' + +describe('SemanticScorer', () => { + it('deve retornar score alto para textos identicos', () => { + const score = SemanticScorer.score( + 'inteligencia artificial moderna', + 'inteligencia artificial moderna', + ) + expect(score).toBe(1.0) + }) + + it('deve retornar 0 para textos sem overlap', () => { + const score = SemanticScorer.score('gato bonito peludo', 'carro veloz vermelho') + expect(score).toBe(0) + }) + + it('deve retornar score parcial para sobreposicao parcial', () => { + const score = SemanticScorer.score( + 'inteligencia artificial moderna avancada', + 'inteligencia artificial classica basica', + ) + expect(score).toBeGreaterThan(0) + expect(score).toBeLessThan(1) + }) + + it('deve ignorar palavras com 2 ou menos caracteres', () => { + const score = SemanticScorer.score('eu sou um dev', 'eu sou um dev') + // "eu", "um" sao filtradas (<=2 chars), apenas "sou" e "dev" contam + expect(score).toBe(1.0) + }) + + it('deve ser case insensitive', () => { + const score = SemanticScorer.score( + 'MACHINE LEARNING DEEP LEARNING', + 'machine learning deep learning', + ) + expect(score).toBe(1.0) + }) + + it('deve retornar 0 para expected vazio', () => { + const score = SemanticScorer.score('qualquer texto', '') + expect(score).toBe(0) + }) + + it('deve retornar 0 para ambos vazios', () => { + const score = SemanticScorer.score('', '') + expect(score).toBe(0) + }) + + it('deve retornar 0 quando actual esta vazio mas expected nao', () => { + const score = SemanticScorer.score('', 'inteligencia artificial') + expect(score).toBe(0) + }) + + it('deve lidar com caracteres especiais', () => { + const score = SemanticScorer.score( + 'café résumé naïve', + 'café résumé naïve', + ) + expect(score).toBe(1.0) + }) + + it('deve retornar score maximo de 1.0 mesmo com overlap total', () => { + const score = SemanticScorer.score( + 'inteligencia artificial machine learning deep learning data science', + 'inteligencia artificial', + ) + expect(score).toBeLessThanOrEqual(1.0) + }) + + it('deve funcionar com textos longos', () => { + const longText = Array.from({ length: 100 }, (_, i) => `palavra${i}`).join(' ') + const score = SemanticScorer.score(longText, longText) + expect(score).toBe(1.0) + }) +}) diff --git a/promptforge/tests/unit/snippetGenerator.extended.test.ts b/promptforge/tests/unit/snippetGenerator.extended.test.ts new file mode 100644 index 0000000..1397925 --- /dev/null +++ b/promptforge/tests/unit/snippetGenerator.extended.test.ts @@ -0,0 +1,149 @@ +import { describe, it, expect } from 'vitest' +import { generateSnippet } from '../../src/utils/snippetGenerator' + +const base = { + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: 'Assistente', + userInput: 'Ola', +} + +describe('snippetGenerator - edge cases', () => { + it('deve escapar aspas duplas em prompts para cURL', () => { + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'anthropic', + systemPrompt: 'Diga "ola" e "tchau"', + }) + expect(code).toContain('\\"ola\\"') + expect(code).toContain('\\"tchau\\"') + expect(code).not.toContain('"ola"') + }) + + it('deve escapar newlines em prompts para cURL', () => { + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'openai', + systemPrompt: 'Linha 1\nLinha 2\nLinha 3', + }) + expect(code).toContain('\\n') + expect(code).not.toContain('\n", "') + }) + + it('deve escapar tabs em prompts para cURL', () => { + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'google', + systemPrompt: 'Campo\tValor', + }) + expect(code).toContain('\\t') + }) + + it('deve escapar carriage returns', () => { + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'anthropic', + systemPrompt: 'Texto\r\ncom CR', + }) + expect(code).toContain('\\r') + }) + + it('deve lidar com prompt vazio', () => { + const code = generateSnippet({ + ...base, + language: 'typescript', + provider: 'anthropic', + systemPrompt: '', + userInput: '', + }) + expect(code).toBeDefined() + expect(code.length).toBeGreaterThan(20) + }) + + it('deve lidar com caracteres Unicode (emoji)', () => { + const code = generateSnippet({ + ...base, + language: 'python', + provider: 'google', + systemPrompt: 'Responda com emojis 🔥🎉', + }) + expect(code).toContain('🔥🎉') + }) + + it('deve lidar com acentos e caracteres especiais', () => { + const code = generateSnippet({ + ...base, + language: 'typescript', + provider: 'openai', + systemPrompt: 'Résumé café naïve über', + }) + expect(code).toContain('Résumé café naïve über') + }) + + it('deve lidar com maxTokens zero', () => { + const code = generateSnippet({ + ...base, + language: 'python', + provider: 'anthropic', + params: { ...base.params, maxTokens: 0 }, + }) + expect(code).toContain('0') + }) + + it('deve lidar com maxTokens muito grande', () => { + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'anthropic', + params: { ...base.params, maxTokens: 1000000 }, + }) + expect(code).toContain('1000000') + }) + + it('deve gerar output valido para todas as 9 combinacoes', () => { + const languages = ['typescript', 'python', 'curl'] as const + const providers = ['anthropic', 'openai', 'google'] as const + for (const language of languages) { + for (const provider of providers) { + const code = generateSnippet({ ...base, language, provider }) + expect(code.length).toBeGreaterThan(50) + // Verificar que variaveis de template foram substituidas + expect(code).not.toContain('{{model}}') + expect(code).not.toContain('{{maxTokens}}') + } + } + }) + + it('deve lancar erro para linguagem invalida', () => { + expect(() => generateSnippet({ ...base, language: 'ruby' as any, provider: 'anthropic' })).toThrow() + }) + + it('deve lancar erro para provider invalido', () => { + expect(() => generateSnippet({ ...base, language: 'typescript', provider: 'azure' as any })).toThrow() + }) + + it('deve incluir modelo correto no snippet gerado', () => { + const code = generateSnippet({ + ...base, + language: 'python', + provider: 'anthropic', + model: 'claude-haiku-4-5', + }) + expect(code).toContain('claude-haiku-4-5') + }) + + it('deve usar escapeForJSON baseado em JSON.stringify', () => { + // Testa que backslashes sao escapados corretamente + const code = generateSnippet({ + ...base, + language: 'curl', + provider: 'anthropic', + systemPrompt: 'Path: C:\\Users\\test', + }) + expect(code).toContain('C:\\\\Users\\\\test') + }) +}) diff --git a/promptforge/tests/unit/snippetGenerator.test.ts b/promptforge/tests/unit/snippetGenerator.test.ts new file mode 100644 index 0000000..ba36523 --- /dev/null +++ b/promptforge/tests/unit/snippetGenerator.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect } from 'vitest' +import { generateSnippet } from '../../src/utils/snippetGenerator' + +const basePayload = { + model: 'claude-opus-4-6', + params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 }, + systemPrompt: 'Voce e um assistente.', + userInput: 'Ola, tudo bem?', +} + +describe('generateSnippet', () => { + it('deve gerar snippet TypeScript/Anthropic', () => { + const code = generateSnippet({ + ...basePayload, + language: 'typescript', + provider: 'anthropic', + }) + expect(code).toContain("import Anthropic from '@anthropic-ai/sdk'") + expect(code).toContain('claude-opus-4-6') + expect(code).toContain('4096') + }) + + it('deve gerar snippet Python/OpenAI', () => { + const code = generateSnippet({ + ...basePayload, + language: 'python', + provider: 'openai', + model: 'gpt-5.4', + }) + expect(code).toContain('from openai import OpenAI') + expect(code).toContain('gpt-5.4') + }) + + it('deve gerar snippet cURL/Google', () => { + const code = generateSnippet({ + ...basePayload, + language: 'curl', + provider: 'google', + model: 'gemini-3.1-pro-preview', + }) + expect(code).toContain('generativelanguage.googleapis.com') + expect(code).toContain('gemini-3.1-pro-preview') + }) + + it('deve gerar todas as 9 combinacoes sem erro', () => { + const languages = ['typescript', 'python', 'curl'] as const + const providers = ['anthropic', 'openai', 'google'] as const + + for (const language of languages) { + for (const provider of providers) { + const code = generateSnippet({ ...basePayload, language, provider }) + expect(code.length).toBeGreaterThan(50) + } + } + }) + + it('deve escapar aspas para cURL', () => { + const code = generateSnippet({ + ...basePayload, + language: 'curl', + provider: 'anthropic', + systemPrompt: 'Diga "ola"', + }) + expect(code).toContain('\\"ola\\"') + }) + + it('deve lancar erro para combinacao invalida', () => { + expect(() => + generateSnippet({ + ...basePayload, + language: 'ruby' as any, + provider: 'anthropic', + }), + ).toThrow() + }) +}) diff --git a/promptforge/tests/unit/streamBridge.extended.test.ts b/promptforge/tests/unit/streamBridge.extended.test.ts new file mode 100644 index 0000000..3462174 --- /dev/null +++ b/promptforge/tests/unit/streamBridge.extended.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, vi } from 'vitest' +import { streamToWebview } from '../../src/utils/streamBridge' + +function costFn(input: number, output: number) { + return { inputTokens: input, outputTokens: output, inputCostUSD: 0, outputCostUSD: 0, totalCostUSD: 0 } +} + +describe('streamToWebview - edge cases avancados', () => { + it('deve lidar com postMessage que falha silenciosamente', async () => { + async function* gen() { yield 'data' } + const panel = { + webview: { + postMessage: vi.fn(() => Promise.resolve(false)), // false = msg nao entregue + }, + } + // Nao deve lancar erro mesmo que postMessage retorne false + await expect(streamToWebview(gen(), panel as any, 's1', costFn, 10)).resolves.not.toThrow() + }) + + it('deve propagar excecao quando postMessage lanca (panel disposed)', async () => { + async function* gen() { yield 'data' } + const panel = { + webview: { + postMessage: vi.fn(() => { throw new Error('Panel disposed') }), + }, + } + // Se postMessage lanca no try, o catch tambem falha ao tentar postar ERROR + await expect(streamToWebview(gen(), panel as any, 's1', costFn, 10)).rejects.toThrow('Panel disposed') + }) + + it('deve acumular tokens de output proporcionalmente ao tamanho dos chunks', async () => { + async function* gen() { + yield 'Hello' // ~2 tokens (5/4 = 1.25, ceil = 2) + yield 'World World World World' // ~6 tokens (24/4 = 6) + } + const msgs: unknown[] = [] + const panel = { + webview: { postMessage: vi.fn((msg: unknown) => { msgs.push(msg); return Promise.resolve(true) }) }, + } + await streamToWebview(gen(), panel as any, 's1', costFn, 100) + const done = (msgs as any[]).find((m) => m.type === 'STREAM_DONE') + expect(done.payload.cost.outputTokens).toBeGreaterThan(0) + }) + + it('deve gerar streamId correto em todos os chunks', async () => { + async function* gen() { yield 'a'; yield 'b'; yield 'c' } + const msgs: unknown[] = [] + const panel = { + webview: { postMessage: vi.fn((msg: unknown) => { msgs.push(msg); return Promise.resolve(true) }) }, + } + await streamToWebview(gen(), panel as any, 'unique-id-123', costFn, 10) + for (const msg of msgs as any[]) { + expect(msg.payload.streamId).toBe('unique-id-123') + } + }) + + it('deve tratar generator que lanca na primeira iteracao', async () => { + async function* gen() { + throw new Error('Immediate failure') + } + const msgs: unknown[] = [] + const panel = { + webview: { postMessage: vi.fn((msg: unknown) => { msgs.push(msg); return Promise.resolve(true) }) }, + } + await streamToWebview(gen(), panel as any, 's1', costFn, 10) + expect((msgs as any[])[0].type).toBe('ERROR') + expect((msgs as any[])[0].payload.message).toContain('Immediate failure') + }) + + it('deve tratar generator com muitos chunks pequenos', async () => { + async function* gen() { + for (let i = 0; i < 100; i++) yield String.fromCharCode(65 + (i % 26)) + } + const msgs: unknown[] = [] + const panel = { + webview: { postMessage: vi.fn((msg: unknown) => { msgs.push(msg); return Promise.resolve(true) }) }, + } + await streamToWebview(gen(), panel as any, 's1', costFn, 10) + const chunks = (msgs as any[]).filter((m) => m.type === 'STREAM_CHUNK') + expect(chunks).toHaveLength(100) + const done = (msgs as any[]).find((m) => m.type === 'STREAM_DONE') + expect(done.payload.fullText).toHaveLength(100) + }) +}) diff --git a/promptforge/tests/unit/streamBridge.test.ts b/promptforge/tests/unit/streamBridge.test.ts new file mode 100644 index 0000000..96d0acd --- /dev/null +++ b/promptforge/tests/unit/streamBridge.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect, vi } from 'vitest' +import { streamToWebview } from '../../src/utils/streamBridge' + +function createMockPanel() { + const messages: unknown[] = [] + return { + webview: { + postMessage: vi.fn((msg: unknown) => { + messages.push(msg) + return Promise.resolve(true) + }), + }, + messages, + } +} + +function costCalculator(input: number, output: number) { + return { + inputTokens: input, + outputTokens: output, + inputCostUSD: input * 0.000005, + outputCostUSD: output * 0.000025, + totalCostUSD: input * 0.000005 + output * 0.000025, + } +} + +describe('streamToWebview', () => { + it('deve enviar chunks e STREAM_DONE para stream normal', async () => { + async function* gen() { + yield 'Hello ' + yield 'World' + } + const panel = createMockPanel() + await streamToWebview(gen(), panel as any, 'stream-1', costCalculator, 100) + + const msgs = panel.messages as any[] + expect(msgs).toHaveLength(3) + expect(msgs[0].type).toBe('STREAM_CHUNK') + expect(msgs[0].payload.chunk).toBe('Hello ') + expect(msgs[0].payload.streamId).toBe('stream-1') + expect(msgs[1].type).toBe('STREAM_CHUNK') + expect(msgs[1].payload.chunk).toBe('World') + expect(msgs[2].type).toBe('STREAM_DONE') + expect(msgs[2].payload.fullText).toBe('Hello World') + expect(msgs[2].payload.streamId).toBe('stream-1') + expect(msgs[2].payload.cost).toBeDefined() + expect(msgs[2].payload.cost.inputTokens).toBe(100) + }) + + it('deve enviar STREAM_DONE com texto vazio para stream vazio', async () => { + async function* gen() { + // nada + } + const panel = createMockPanel() + await streamToWebview(gen(), panel as any, 'stream-empty', costCalculator, 50) + + const msgs = panel.messages as any[] + expect(msgs).toHaveLength(1) + expect(msgs[0].type).toBe('STREAM_DONE') + expect(msgs[0].payload.fullText).toBe('') + }) + + it('deve enviar ERROR quando generator lanca excecao', async () => { + async function* gen() { + yield 'partial' + throw new Error('API timeout') + } + const panel = createMockPanel() + await streamToWebview(gen(), panel as any, 'stream-err', costCalculator, 10) + + const msgs = panel.messages as any[] + const errorMsg = msgs.find((m: any) => m.type === 'ERROR') + expect(errorMsg).toBeDefined() + expect(errorMsg.payload.message).toContain('API timeout') + expect(errorMsg.payload.code).toBe('STREAM_ERROR') + }) + + it('deve calcular custo corretamente no callback', async () => { + async function* gen() { + yield 'test output here' + } + const panel = createMockPanel() + const customCost = vi.fn((input: number, output: number) => ({ + inputTokens: input, + outputTokens: output, + inputCostUSD: 0.01, + outputCostUSD: 0.02, + totalCostUSD: 0.03, + })) + await streamToWebview(gen(), panel as any, 's1', customCost, 200) + + expect(customCost).toHaveBeenCalledOnce() + expect(customCost).toHaveBeenCalledWith(200, expect.any(Number)) + const doneMsg = (panel.messages as any[]).find((m: any) => m.type === 'STREAM_DONE') + expect(doneMsg.payload.cost.totalCostUSD).toBe(0.03) + }) + + it('deve acumular fullText de multiplos chunks corretamente', async () => { + async function* gen() { + yield 'A' + yield 'B' + yield 'C' + yield 'D' + yield 'E' + } + const panel = createMockPanel() + await streamToWebview(gen(), panel as any, 's2', costCalculator, 10) + + const doneMsg = (panel.messages as any[]).find((m: any) => m.type === 'STREAM_DONE') + expect(doneMsg.payload.fullText).toBe('ABCDE') + }) + + it('deve lidar com chunks contendo caracteres especiais', async () => { + async function* gen() { + yield 'Olá ' + yield '🔥 ' + yield 'café\n' + } + const panel = createMockPanel() + await streamToWebview(gen(), panel as any, 's3', costCalculator, 10) + + const doneMsg = (panel.messages as any[]).find((m: any) => m.type === 'STREAM_DONE') + expect(doneMsg.payload.fullText).toBe('Olá 🔥 café\n') + }) +}) diff --git a/promptforge/tests/unit/testPanel.test.ts b/promptforge/tests/unit/testPanel.test.ts new file mode 100644 index 0000000..cab8c4b --- /dev/null +++ b/promptforge/tests/unit/testPanel.test.ts @@ -0,0 +1,129 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mocks = vi.hoisted(() => ({ + postMessage: vi.fn(), + onDidReceiveMessage: vi.fn(), + onDidDispose: vi.fn(), + executeCommand: vi.fn(), + createWebviewPanel: vi.fn(), + html: '', +})) + +vi.mock('vscode', () => ({ + window: { + activeTextEditor: undefined, + createWebviewPanel: (...args: unknown[]) => { + mocks.createWebviewPanel(...args) + return { + webview: { + set html(val: string) { mocks.html = val }, + get html() { return mocks.html }, + postMessage: mocks.postMessage, + onDidReceiveMessage: mocks.onDidReceiveMessage, + asWebviewUri: (u: any) => u?.toString?.() ?? String(u), + cspSource: 'csp', + }, + onDidDispose: mocks.onDidDispose, + reveal: vi.fn(), + dispose: vi.fn(), + } + }, + }, + commands: { executeCommand: mocks.executeCommand }, + ViewColumn: { One: 1 }, + Uri: { joinPath: (...p: any[]) => ({ fsPath: p.map((x: any) => x?.fsPath ?? x).join('/'), toString: () => p.join('/') }) }, + workspace: { workspaceFolders: [{ uri: { fsPath: '/tmp' } }] }, +})) + +vi.mock('../../src/providers/ModelProvider', () => ({ + ProviderFactory: { create: vi.fn(() => ({ + complete: vi.fn(async () => ({ text: 'Output', inputTokens: 200, outputTokens: 100, model: 'm', latencyMs: 500 })), + stream: vi.fn(async function*() { yield 'x' }), + })) }, +})) +vi.mock('../../src/utils/tokenCounter', () => ({ + calculateCost: vi.fn(() => ({ inputTokens: 200, outputTokens: 100, inputCostUSD: 0.001, outputCostUSD: 0.002, totalCostUSD: 0.003 })), +})) +vi.mock('../../src/commands/configureKeys', () => ({ + getApiKey: vi.fn(async () => 'sk-test'), setExtensionContext: vi.fn(), +})) +vi.mock('../../src/utils/variableParser', () => ({ resolveVariables: vi.fn((t: string) => t) })) +vi.mock('../../src/utils/streamBridge', () => ({ streamToWebview: vi.fn(async () => {}) })) +vi.mock('../../src/storage/ReplayStore', () => ({ + ReplayStore: { initialize: vi.fn(), getInstance: vi.fn(() => ({ + captureReplay: vi.fn(async () => 'r-id'), + loadReplays: vi.fn(async () => [{ id: 'r1', capturedAt: '2025-01-01', model: 'm', params: {}, systemPrompt: 's', userInput: 'u', originalOutput: 'o', originalCostUSD: 0.001, originalLatencyMs: 500 }]), + })) }, +})) + +import { TestPanel } from '../../src/panels/TestPanel' + +const tick = () => new Promise((r) => setTimeout(r, 100)) + +beforeEach(() => { vi.clearAllMocks(); mocks.html = ''; TestPanel.currentPanel = undefined }) + +const payload = { systemPrompt: 'S', userPromptTemplate: 'T', variables: {}, userInput: 'I', model: 'claude-opus-4-6', params: { model: 'claude-opus-4-6', temperature: 1.0, maxTokens: 4096 } } + +function callHandler(msg: unknown) { + const handler = mocks.onDidReceiveMessage.mock.calls[0][0] + handler(msg) +} + +describe('TestPanel', () => { + it('deve criar panel com CSP', () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.html).toContain('Content-Security-Policy') + }) + + it('deve ser singleton', () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + TestPanel.createOrShow({ fsPath: '/ext' } as any) + expect(mocks.createWebviewPanel).toHaveBeenCalledOnce() + }) + + it('deve tratar CONFIGURE_KEYS sincronamente', async () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'CONFIGURE_KEYS', payload: undefined }) + await tick() + expect(mocks.executeCommand).toHaveBeenCalledWith('promptforge.configureKeys') + }) + + it('deve tratar LOAD_REPLAYS', async () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'LOAD_REPLAYS', payload: undefined }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'REPLAYS_DATA' })) + }) + + it('deve tratar RUN_TEST', async () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'RUN_TEST', payload }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'TEST_RESULT', payload: expect.objectContaining({ output: 'Output' }) })) + }) + + it('deve enviar ERROR sem API key', async () => { + const { getApiKey } = await import('../../src/commands/configureKeys') + vi.mocked(getApiKey).mockResolvedValueOnce(undefined) + TestPanel.createOrShow({ fsPath: '/ext' } as any) + callHandler({ type: 'RUN_TEST', payload }) + await tick() + expect(mocks.postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'ERROR', payload: expect.objectContaining({ code: 'NO_API_KEY' }) })) + }) + + it('deve tratar CAPTURE_REPLAY sem erro', async () => { + TestPanel.createOrShow({ fsPath: '/ext' } as any) + // Verifica que o handler nao lanca erro ao processar CAPTURE_REPLAY + callHandler({ type: 'CAPTURE_REPLAY', payload: { model: 'm', params: {}, systemPrompt: 's', userInput: 'u', originalOutput: 'o', originalCostUSD: 0, originalLatencyMs: 0 } }) + await tick() + // Se chegou aqui sem erro, o handler processou corretamente + // A persistencia real e testada em replayStore.test.ts + expect(mocks.postMessage).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'ERROR' })) + }) + + it('deve limpar currentPanel ao dispose', () => { + const p = TestPanel.createOrShow({ fsPath: '/ext' } as any) + p.dispose() + expect(TestPanel.currentPanel).toBeUndefined() + }) +}) diff --git a/promptforge/tests/unit/tokenCounter.extended.test.ts b/promptforge/tests/unit/tokenCounter.extended.test.ts new file mode 100644 index 0000000..7c05984 --- /dev/null +++ b/promptforge/tests/unit/tokenCounter.extended.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect } from 'vitest' +import { calculateCost, PRICING_USD_PER_1M } from '../../src/utils/tokenCounter' +import { MODEL_CATALOG } from '../../src/types/models' + +describe('calculateCost - todos os modelos', () => { + const allModels = Object.values(MODEL_CATALOG).flat().map((m) => m.id) + + it('deve ter precos para todos os modelos do catalogo (exceto openrouter)', () => { + for (const model of allModels) { + if (model.startsWith('openrouter')) continue + expect(PRICING_USD_PER_1M[model], `Preco ausente para ${model}`).toBeDefined() + } + }) + + it('todos os precos devem ser positivos', () => { + for (const [model, price] of Object.entries(PRICING_USD_PER_1M)) { + expect(price.input, `${model}.input`).toBeGreaterThan(0) + expect(price.output, `${model}.output`).toBeGreaterThan(0) + } + }) + + it('output deve ser mais caro que input para todos os modelos', () => { + for (const [model, price] of Object.entries(PRICING_USD_PER_1M)) { + expect(price.output, `${model}: output deveria ser >= input`).toBeGreaterThanOrEqual( + price.input, + ) + } + }) +}) + +describe('calculateCost - precisao', () => { + it('deve calcular 1M tokens de input para Claude Opus 4.6 corretamente', () => { + const cost = calculateCost('claude-opus-4-6', 1_000_000, 0) + expect(cost.inputCostUSD).toBe(5.0) + expect(cost.outputCostUSD).toBe(0) + expect(cost.totalCostUSD).toBe(5.0) + }) + + it('deve calcular 1M tokens de output para Claude Opus 4.6 corretamente', () => { + const cost = calculateCost('claude-opus-4-6', 0, 1_000_000) + expect(cost.outputCostUSD).toBe(25.0) + }) + + it('o3 deve ser o modelo mais caro', () => { + const o3Cost = calculateCost('o3', 1000, 1000) + const haikuCost = calculateCost('claude-haiku-4-5', 1000, 1000) + expect(o3Cost.totalCostUSD).toBeGreaterThan(haikuCost.totalCostUSD) + }) + + it('gemini-3.1-flash-lite deve ser o mais barato', () => { + const flashLiteCost = calculateCost('gemini-3.1-flash-lite-preview', 1000, 1000) + for (const model of Object.keys(PRICING_USD_PER_1M)) { + if (model === 'gemini-3.1-flash-lite-preview') continue + const otherCost = calculateCost(model, 1000, 1000) + expect( + flashLiteCost.totalCostUSD, + `${model} deveria ser mais caro que flash-lite`, + ).toBeLessThanOrEqual(otherCost.totalCostUSD) + } + }) +}) + +describe('calculateCost - edge cases', () => { + it('deve retornar zero para zero tokens', () => { + const cost = calculateCost('claude-opus-4-6', 0, 0) + expect(cost.totalCostUSD).toBe(0) + expect(cost.inputTokens).toBe(0) + expect(cost.outputTokens).toBe(0) + }) + + it('deve lidar com tokens negativos sem crashar', () => { + const cost = calculateCost('claude-opus-4-6', -100, -50) + expect(cost.totalCostUSD).toBeLessThan(0) + }) + + it('deve manter precisao para tokens grandes (1 bilhao)', () => { + const cost = calculateCost('gpt-5.4-mini', 1_000_000_000, 0) + expect(cost.inputCostUSD).toBeCloseTo(400.0, 2) + }) + + it('deve usar fallback para modelo desconhecido', () => { + const cost = calculateCost('modelo-futuro', 1_000_000, 1_000_000) + expect(cost.totalCostUSD).toBeGreaterThan(0) + // Fallback: input=1.0, output=5.0 + expect(cost.inputCostUSD).toBe(1.0) + expect(cost.outputCostUSD).toBe(5.0) + }) + + it('deve retornar objeto CostInfo completo', () => { + const cost = calculateCost('claude-sonnet-4-6', 500, 200) + expect(cost).toHaveProperty('inputTokens') + expect(cost).toHaveProperty('outputTokens') + expect(cost).toHaveProperty('inputCostUSD') + expect(cost).toHaveProperty('outputCostUSD') + expect(cost).toHaveProperty('totalCostUSD') + expect(cost.inputTokens).toBe(500) + expect(cost.outputTokens).toBe(200) + expect(cost.totalCostUSD).toBe(cost.inputCostUSD + cost.outputCostUSD) + }) +}) diff --git a/promptforge/tests/unit/tokenCounter.test.ts b/promptforge/tests/unit/tokenCounter.test.ts new file mode 100644 index 0000000..6c962ae --- /dev/null +++ b/promptforge/tests/unit/tokenCounter.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest' +import { calculateCost, PRICING_USD_PER_1M } from '../../src/utils/tokenCounter' + +describe('calculateCost', () => { + it('deve calcular custo para Claude Opus 4.6', () => { + const result = calculateCost('claude-opus-4-6', 1000, 500) + expect(result.inputTokens).toBe(1000) + expect(result.outputTokens).toBe(500) + expect(result.inputCostUSD).toBeCloseTo(0.005, 6) + expect(result.outputCostUSD).toBeCloseTo(0.0125, 6) + expect(result.totalCostUSD).toBeCloseTo(0.0175, 6) + }) + + it('deve calcular custo para GPT-5.4 Mini', () => { + const result = calculateCost('gpt-5.4-mini', 10000, 5000) + expect(result.inputCostUSD).toBeCloseTo(0.004, 6) + expect(result.outputCostUSD).toBeCloseTo(0.008, 6) + }) + + it('deve usar fallback para modelo desconhecido', () => { + const result = calculateCost('modelo-inexistente', 1000, 1000) + expect(result.totalCostUSD).toBeGreaterThan(0) + }) + + it('deve retornar zero para zero tokens', () => { + const result = calculateCost('claude-opus-4-6', 0, 0) + expect(result.totalCostUSD).toBe(0) + }) + + it('deve ter precos para todos os modelos do catalogo', () => { + const expectedModels = [ + 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5', + 'gpt-5.4', 'gpt-5.4-mini', 'o3', 'o4-mini', + 'gemini-3.1-pro-preview', 'gemini-2.5-flash', 'gemini-3.1-flash-lite-preview', + ] + for (const model of expectedModels) { + expect(PRICING_USD_PER_1M[model]).toBeDefined() + expect(PRICING_USD_PER_1M[model].input).toBeGreaterThan(0) + expect(PRICING_USD_PER_1M[model].output).toBeGreaterThan(0) + } + }) +}) diff --git a/promptforge/tests/unit/variableParser.extended.test.ts b/promptforge/tests/unit/variableParser.extended.test.ts new file mode 100644 index 0000000..43e9f6c --- /dev/null +++ b/promptforge/tests/unit/variableParser.extended.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect } from 'vitest' +import { parseVariables, resolveVariables } from '../../src/utils/variableParser' + +describe('parseVariables - edge cases', () => { + it('deve lidar com nome vazio {{}}', () => { + const result = parseVariables('Texto {{}} aqui') + // Regex exige pelo menos 1 char em [^}:]+, entao nao deveria matchear + expect(result).toEqual([]) + }) + + it('deve lidar com espacos {{ nome }}', () => { + const result = parseVariables('Ola {{ nome }} bem-vindo') + expect(result).toHaveLength(1) + expect(result[0].name).toBe('nome') + }) + + it('deve lidar com default vazio {{nome:}}', () => { + const result = parseVariables('{{nome:}}') + expect(result).toHaveLength(1) + expect(result[0].name).toBe('nome') + expect(result[0].defaultValue).toBe('') + }) + + it('deve lidar com default com espacos {{nome:valor com espacos}}', () => { + const result = parseVariables('{{nome:valor com espacos}}') + expect(result).toHaveLength(1) + expect(result[0].defaultValue).toBe('valor com espacos') + }) + + it('deve lidar com tres chaves {{{nome}}}', () => { + const result = parseVariables('{{{nome}}}') + // A regex deve capturar o {{nome}} interno + expect(result.length).toBeGreaterThanOrEqual(0) + }) + + it('deve remover duplicatas preservando primeira ocorrencia', () => { + const result = parseVariables('{{a}} e {{b}} e {{a}} novamente') + expect(result).toHaveLength(2) + expect(result[0].name).toBe('a') + expect(result[1].name).toBe('b') + }) + + it('deve funcionar com muitas variaveis (100+)', () => { + const vars = Array.from({ length: 100 }, (_, i) => `{{var${i}}`).join(' ') + // Note: acima tem chaves incompletas, corrigir + const validVars = Array.from({ length: 100 }, (_, i) => `{{var${i}}}`).join(' ') + const result = parseVariables(validVars) + expect(result).toHaveLength(100) + expect(result[0].name).toBe('var0') + expect(result[99].name).toBe('var99') + }) + + it('deve lidar com dois pontos no default', () => { + // Regex para: nao capturar alem do primeiro } + const result = parseVariables('{{hora:14:30}}') + expect(result).toHaveLength(1) + expect(result[0].name).toBe('hora') + // O regex para no primeiro }, entao "14:30" pode ou nao ser capturado completo + }) + + it('deve lidar com underscores e hifens no nome', () => { + const result = parseVariables('{{nome_completo}} e {{primeiro-nome}}') + expect(result).toHaveLength(2) + expect(result[0].name).toBe('nome_completo') + expect(result[1].name).toBe('primeiro-nome') + }) + + it('deve lidar com texto sem nenhuma variavel', () => { + const result = parseVariables('Apenas texto simples sem variaveis') + expect(result).toEqual([]) + }) + + it('deve lidar com chaves soltas que nao formam variavel', () => { + const result = parseVariables('Texto com { chave unica } e {outra}') + expect(result).toEqual([]) + }) +}) + +describe('resolveVariables - edge cases', () => { + it('deve preservar texto sem variaveis', () => { + const result = resolveVariables('Texto simples', { nome: 'Valor' }) + expect(result).toBe('Texto simples') + }) + + it('deve substituir multiplas ocorrencias da mesma variavel', () => { + const result = resolveVariables('{{nome}} gosta de {{nome}}', { nome: 'Ana' }) + expect(result).toBe('Ana gosta de Ana') + }) + + it('deve manter variavel quando sem valor e sem padrao', () => { + const result = resolveVariables('Ola {{nome}}!', {}) + expect(result).toBe('Ola {{nome}}!') + }) + + it('deve usar default quando valor e string vazia no mapa', () => { + const result = resolveVariables('{{nome:padrao}}', { nome: '' }) + // String vazia e um valor valido + expect(result).toBe('') + }) + + it('deve resolver variaveis com espacos no nome', () => { + const result = resolveVariables('{{ nome }}', { nome: 'Maria' }) + expect(result).toBe('Maria') + }) + + it('deve resolver variaveis misturadas com e sem padrao', () => { + const result = resolveVariables( + '{{nome}} trabalha em {{empresa:Acme}} como {{cargo}}', + { nome: 'Ana', cargo: 'Dev' }, + ) + expect(result).toBe('Ana trabalha em Acme como Dev') + }) +}) diff --git a/promptforge/tests/unit/variableParser.test.ts b/promptforge/tests/unit/variableParser.test.ts new file mode 100644 index 0000000..0d6af8e --- /dev/null +++ b/promptforge/tests/unit/variableParser.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest' +import { parseVariables, resolveVariables } from '../../src/utils/variableParser' + +describe('parseVariables', () => { + it('deve detectar variaveis simples', () => { + const result = parseVariables('Ola {{nome}}, bem-vindo!') + expect(result).toEqual([{ name: 'nome', defaultValue: undefined }]) + }) + + it('deve detectar variaveis com valor padrao', () => { + const result = parseVariables('Idioma: {{idioma:portugues}}') + expect(result).toEqual([{ name: 'idioma', defaultValue: 'portugues' }]) + }) + + it('deve detectar multiplas variaveis', () => { + const result = parseVariables('{{nome}} trabalha em {{empresa:Acme}} como {{cargo}}') + expect(result).toHaveLength(3) + expect(result[0].name).toBe('nome') + expect(result[1].name).toBe('empresa') + expect(result[1].defaultValue).toBe('Acme') + expect(result[2].name).toBe('cargo') + }) + + it('deve remover duplicatas', () => { + const result = parseVariables('{{nome}} e {{nome}} novamente') + expect(result).toHaveLength(1) + }) + + it('deve retornar vazio para texto sem variaveis', () => { + const result = parseVariables('Texto simples sem variaveis') + expect(result).toEqual([]) + }) +}) + +describe('resolveVariables', () => { + it('deve substituir variaveis por valores', () => { + const result = resolveVariables('Ola {{nome}}!', { nome: 'Maria' }) + expect(result).toBe('Ola Maria!') + }) + + it('deve usar valor padrao quando nao fornecido', () => { + const result = resolveVariables('Idioma: {{idioma:portugues}}', {}) + expect(result).toBe('Idioma: portugues') + }) + + it('deve manter variavel quando sem valor e sem padrao', () => { + const result = resolveVariables('Ola {{nome}}!', {}) + expect(result).toBe('Ola {{nome}}!') + }) + + it('deve substituir multiplas variaveis', () => { + const result = resolveVariables('{{nome}} - {{cargo}}', { nome: 'Ana', cargo: 'Dev' }) + expect(result).toBe('Ana - Dev') + }) +}) diff --git a/promptforge/tsconfig.json b/promptforge/tsconfig.json new file mode 100644 index 0000000..a63f7fe --- /dev/null +++ b/promptforge/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "sourceMap": true, + "declaration": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "types": ["node", "vscode"] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "webview", "tests"] +} diff --git a/promptforge/tsconfig.webview.json b/promptforge/tsconfig.webview.json new file mode 100644 index 0000000..f0812b9 --- /dev/null +++ b/promptforge/tsconfig.webview.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "moduleResolution": "bundler", + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist/webview", + "rootDir": "./webview/src", + "types": ["react", "react-dom"] + }, + "include": ["webview/src/**/*.ts", "webview/src/**/*.tsx", "src/types/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/promptforge/vitest.config.ts b/promptforge/vitest.config.ts new file mode 100644 index 0000000..dd1d0bb --- /dev/null +++ b/promptforge/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['tests/**/*.test.ts'], + exclude: ['node_modules', 'dist'], + }, +}) diff --git a/promptforge/webview/index.html b/promptforge/webview/index.html new file mode 100644 index 0000000..6d7bbfd --- /dev/null +++ b/promptforge/webview/index.html @@ -0,0 +1,12 @@ + + + + + + PromptForge + + +
+ + + diff --git a/promptforge/webview/src/App.tsx b/promptforge/webview/src/App.tsx new file mode 100644 index 0000000..fc5a0c7 --- /dev/null +++ b/promptforge/webview/src/App.tsx @@ -0,0 +1,124 @@ +import React, { useCallback } from 'react' +import { usePromptStore } from './store/usePromptStore' +import { useVSCodeMessage } from './hooks/useVSCodeMessage' +import { OnboardingBanner } from './components/OnboardingBanner' +import { EditorMode } from './modes/Editor/EditorMode' +import { TestMode } from './modes/Test/TestMode' +import { LibraryMode } from './modes/Library/LibraryMode' + +type AppMode = 'editor' | 'test' | 'library' + +const TABS: { key: AppMode; label: string }[] = [ + { key: 'editor', label: 'Editor' }, + { key: 'test', label: 'Teste' }, + { key: 'library', label: 'Biblioteca' }, +] + +export function App() { + const store = usePromptStore() + + const handleMessage = useCallback( + (msg: any) => { + if (!msg || !msg.type) return + + switch (msg.type) { + case 'STREAM_CHUNK': + store.handleStreamChunk(msg.payload) + break + case 'STREAM_DONE': + store.handleStreamDone(msg.payload) + break + case 'REFINE_RESULT': + store.handleStreamDone(msg.payload) + break + case 'TEST_RESULT': + store.addTestResult(msg.payload) + break + case 'AB_RESULT': + store.setABResult(msg.payload) + break + case 'LIBRARY_DATA': + store.setLibrary(msg.payload.prompts) + break + case 'REPLAYS_DATA': + store.setReplays(msg.payload.replays) + break + case 'KEYS_STATUS': + store.setKeysStatus(msg.payload) + break + case 'PROMPT_SAVED': + store.setLastSavedPromptId(msg.payload.promptId) + break + case 'SNIPPET_RESULT': + store.setSnippetResult(msg.payload.code, msg.payload.language) + break + case 'ERROR': + store.setLastError(msg.payload.message) + console.error('[PromptForge]', msg.payload.message) + break + } + }, + [store], + ) + + const { postMessage } = useVSCodeMessage(handleMessage) + + return ( +
+ {/* Header */} +
+
+ PromptForge +
+ {TABS.map((tab) => ( + + ))} +
+
+ +
+ + {/* Banner de onboarding */} +
+ postMessage({ type: 'CONFIGURE_KEYS', payload: undefined })} + /> +
+ + {/* Notificacoes */} + {store.lastError && ( +
+ {store.lastError} + +
+ )} + {store.lastSavedPromptId && ( +
+ Prompt salvo com sucesso! + +
+ )} + + {/* Conteudo principal */} + {store.mode === 'editor' && } + {store.mode === 'test' && } + {store.mode === 'library' && } +
+ ) +} diff --git a/promptforge/webview/src/components/DiffViewer.tsx b/promptforge/webview/src/components/DiffViewer.tsx new file mode 100644 index 0000000..7bbbf6f --- /dev/null +++ b/promptforge/webview/src/components/DiffViewer.tsx @@ -0,0 +1,45 @@ +import React from 'react' + +interface Props { + original: string + modified: string + labelA?: string + labelB?: string +} + +export function DiffViewer({ original, modified, labelA = 'Original', labelB = 'Novo' }: Props) { + const linesA = original.split('\n') + const linesB = modified.split('\n') + const maxLines = Math.max(linesA.length, linesB.length) + + return ( +
+
+
{labelA}
+ {Array.from({ length: maxLines }, (_, i) => { + const line = linesA[i] ?? '' + const isDiff = line !== (linesB[i] ?? '') + return ( +
+ {i + 1} + {line || '\u00A0'} +
+ ) + })} +
+
+
{labelB}
+ {Array.from({ length: maxLines }, (_, i) => { + const line = linesB[i] ?? '' + const isDiff = line !== (linesA[i] ?? '') + return ( +
+ {i + 1} + {line || '\u00A0'} +
+ ) + })} +
+
+ ) +} diff --git a/promptforge/webview/src/components/LoadingStream.tsx b/promptforge/webview/src/components/LoadingStream.tsx new file mode 100644 index 0000000..6f723d3 --- /dev/null +++ b/promptforge/webview/src/components/LoadingStream.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +interface Props { + isStreaming: boolean + label?: string +} + +export function LoadingStream({ isStreaming, label = 'Gerando...' }: Props) { + if (!isStreaming) return null + + return ( +
+
+ + + +
+ {label} +
+ ) +} diff --git a/promptforge/webview/src/components/ModelBadge.tsx b/promptforge/webview/src/components/ModelBadge.tsx new file mode 100644 index 0000000..ca1e9a2 --- /dev/null +++ b/promptforge/webview/src/components/ModelBadge.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +interface Props { + modelId: string + size?: 'sm' | 'md' +} + +const MODEL_COLORS: Record = { + claude: 'bg-orange-900/50 text-orange-300 border-orange-700', + gpt: 'bg-green-900/50 text-green-300 border-green-700', + o3: 'bg-purple-900/50 text-purple-300 border-purple-700', + 'o4-mini': 'bg-purple-900/50 text-purple-300 border-purple-700', + gemini: 'bg-blue-900/50 text-blue-300 border-blue-700', + openrouter: 'bg-gray-900/50 text-gray-300 border-gray-700', +} + +function getColorClass(modelId: string): string { + for (const [prefix, cls] of Object.entries(MODEL_COLORS)) { + if (modelId.startsWith(prefix) || modelId === prefix) return cls + } + return 'bg-gray-900/50 text-gray-300 border-gray-700' +} + +export function ModelBadge({ modelId, size = 'sm' }: Props) { + const sizeClass = size === 'sm' ? 'text-xs px-2 py-0.5' : 'text-sm px-3 py-1' + return ( + + {modelId} + + ) +} diff --git a/promptforge/webview/src/components/OnboardingBanner.tsx b/promptforge/webview/src/components/OnboardingBanner.tsx new file mode 100644 index 0000000..871d2d6 --- /dev/null +++ b/promptforge/webview/src/components/OnboardingBanner.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +interface Props { + keysStatus: { anthropic: boolean; openai: boolean; google: boolean; openrouter: boolean } + onConfigure: () => void +} + +export function OnboardingBanner({ keysStatus, onConfigure }: Props) { + const hasAnyKey = keysStatus.anthropic || keysStatus.openai || keysStatus.google || keysStatus.openrouter + if (hasAnyKey) return null + + return ( +
+

Bem-vindo ao PromptForge!

+

+ Configure pelo menos uma API key para comecar a usar o co-escritor de prompts. +

+ +
+ ) +} diff --git a/promptforge/webview/src/components/TokenCostDisplay.tsx b/promptforge/webview/src/components/TokenCostDisplay.tsx new file mode 100644 index 0000000..c22b851 --- /dev/null +++ b/promptforge/webview/src/components/TokenCostDisplay.tsx @@ -0,0 +1,35 @@ +import React from 'react' + +interface CostInfo { + inputTokens: number + outputTokens: number + inputCostUSD: number + outputCostUSD: number + totalCostUSD: number +} + +interface Props { + cost: CostInfo + latencyMs?: number +} + +export function TokenCostDisplay({ cost, latencyMs }: Props) { + return ( +
+ + IN: {cost.inputTokens.toLocaleString()} + + + OUT: {cost.outputTokens.toLocaleString()} + + + ${cost.totalCostUSD.toFixed(4)} + + {latencyMs !== undefined && ( + + {latencyMs}ms + + )} +
+ ) +} diff --git a/promptforge/webview/src/hooks/useStreaming.ts b/promptforge/webview/src/hooks/useStreaming.ts new file mode 100644 index 0000000..42b49e4 --- /dev/null +++ b/promptforge/webview/src/hooks/useStreaming.ts @@ -0,0 +1,66 @@ +import { useState, useCallback, useRef } from 'react' + +interface StreamState { + text: string + isStreaming: boolean + streamId: string | null +} + +interface CostInfo { + inputTokens: number + outputTokens: number + inputCostUSD: number + outputCostUSD: number + totalCostUSD: number +} + +interface StreamDonePayload { + fullText: string + streamId: string + cost: CostInfo +} + +export function useStreaming() { + const [state, setState] = useState({ + text: '', + isStreaming: false, + streamId: null, + }) + const [lastCost, setLastCost] = useState(null) + const textRef = useRef('') + + const handleChunk = useCallback((payload: { chunk: string; streamId: string }) => { + textRef.current += payload.chunk + setState({ + text: textRef.current, + isStreaming: true, + streamId: payload.streamId, + }) + }, []) + + const handleDone = useCallback((payload: StreamDonePayload) => { + textRef.current = payload.fullText + setState({ + text: payload.fullText, + isStreaming: false, + streamId: null, + }) + setLastCost(payload.cost) + }, []) + + const reset = useCallback(() => { + textRef.current = '' + setState({ text: '', isStreaming: false, streamId: null }) + setLastCost(null) + }, []) + + return { + text: state.text, + isStreaming: state.isStreaming, + streamId: state.streamId, + lastCost, + handleChunk, + handleDone, + reset, + } +} diff --git a/promptforge/webview/src/hooks/useVSCodeMessage.ts b/promptforge/webview/src/hooks/useVSCodeMessage.ts new file mode 100644 index 0000000..9f93588 --- /dev/null +++ b/promptforge/webview/src/hooks/useVSCodeMessage.ts @@ -0,0 +1,36 @@ +import { useEffect, useCallback } from 'react' + +interface VSCodeAPI { + postMessage(message: unknown): void + getState(): unknown + setState(state: unknown): void +} + +declare function acquireVsCodeApi(): VSCodeAPI + +let vscodeApi: VSCodeAPI | null = null + +function getVSCodeAPI(): VSCodeAPI { + if (!vscodeApi) { + vscodeApi = acquireVsCodeApi() + } + return vscodeApi +} + +export function useVSCodeMessage( + handler: (message: T) => void, +): { postMessage: (message: unknown) => void } { + useEffect(() => { + const listener = (event: MessageEvent) => { + handler(event.data as T) + } + window.addEventListener('message', listener) + return () => window.removeEventListener('message', listener) + }, [handler]) + + const postMessage = useCallback((message: unknown) => { + getVSCodeAPI().postMessage(message) + }, []) + + return { postMessage } +} diff --git a/promptforge/webview/src/index.css b/promptforge/webview/src/index.css new file mode 100644 index 0000000..1c87e0c --- /dev/null +++ b/promptforge/webview/src/index.css @@ -0,0 +1,15 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + padding: 0; + background-color: #1a1a1a; + color: #e5e5e5; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} + +.font-code { + font-family: 'JetBrains Mono', 'Cascadia Code', monospace; +} diff --git a/promptforge/webview/src/main.tsx b/promptforge/webview/src/main.tsx new file mode 100644 index 0000000..99a0357 --- /dev/null +++ b/promptforge/webview/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { createRoot } from 'react-dom/client' +import { App } from './App' + +const container = document.getElementById('root') +if (container) { + const root = createRoot(container) + root.render( + + + , + ) +} diff --git a/promptforge/webview/src/modes/Editor/CoWriter.tsx b/promptforge/webview/src/modes/Editor/CoWriter.tsx new file mode 100644 index 0000000..f805e26 --- /dev/null +++ b/promptforge/webview/src/modes/Editor/CoWriter.tsx @@ -0,0 +1,125 @@ +import React, { useCallback } from 'react' +import { usePromptStore } from '../../store/usePromptStore' +import { LoadingStream } from '../../components/LoadingStream' +import { TokenCostDisplay } from '../../components/TokenCostDisplay' +import { PromptPreview } from './PromptPreview' + +interface Props { + postMessage: (msg: unknown) => void +} + +export function CoWriter({ postMessage }: Props) { + const { + systemPrompt, + userPromptTemplate, + setSystemPrompt, + setUserPromptTemplate, + selectedModel, + params, + userIntent, + userContext, + audience, + useCase, + streamText, + streamIsActive, + streamCost, + resetStream, + } = usePromptStore() + + const handleGenerate = useCallback( + (currentDraft?: string) => { + resetStream() + postMessage({ + type: 'GENERATE_PROMPT', + payload: { + userIntent, + userContext, + audience, + useCase, + targetModel: selectedModel, + params, + currentDraft, + }, + }) + }, + [userIntent, userContext, audience, useCase, selectedModel, params, postMessage, resetStream], + ) + + const handleApplyGenerated = useCallback(() => { + if (streamText) { + setSystemPrompt(streamText) + } + }, [streamText, setSystemPrompt]) + + return ( +
+
+ {/* Coluna esquerda: Edicao manual */} +
+
+ +