From e7555e6f417e80754f814dd465eac44e448ce35c Mon Sep 17 00:00:00 2001 From: Bun Bun Date: Tue, 16 Jun 2026 12:30:22 +0000 Subject: [PATCH] feat: Screwdriver v1.0.0 --- .gitignore | 7 + icons/icon128.svg | 1 + icons/icon16.svg | 1 + icons/icon32.svg | 1 + icons/icon48.svg | 1 + manifest.json | 44 + package-lock.json | 2277 +++++++++++++++++++++++++++ package.json | 24 + src/background.ts | 108 ++ src/content/content.css | 4 + src/content/content.ts | 199 +++ src/options/options.css | 146 ++ src/options/options.html | 80 + src/options/options.ts | 63 + src/popup/popup.css | 409 +++++ src/popup/popup.html | 160 ++ src/popup/popup.ts | 290 ++++ src/utils/command-translator.ts | 356 +++++ src/utils/git-conflict-resolver.ts | 159 ++ src/utils/regex-tester.ts | 90 ++ src/utils/storage.ts | 61 + src/utils/text-transformer.ts | 197 +++ tests/command-translator.test.ts | 38 + tests/git-conflict-resolver.test.ts | 46 + tests/regex-tester.test.ts | 39 + tests/text-transformer.test.ts | 56 + tsconfig.json | 20 + vite.config.ts | 16 + 28 files changed, 4893 insertions(+) create mode 100644 .gitignore create mode 100644 icons/icon128.svg create mode 100644 icons/icon16.svg create mode 100644 icons/icon32.svg create mode 100644 icons/icon48.svg create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/background.ts create mode 100644 src/content/content.css create mode 100644 src/content/content.ts create mode 100644 src/options/options.css create mode 100644 src/options/options.html create mode 100644 src/options/options.ts create mode 100644 src/popup/popup.css create mode 100644 src/popup/popup.html create mode 100644 src/popup/popup.ts create mode 100644 src/utils/command-translator.ts create mode 100644 src/utils/git-conflict-resolver.ts create mode 100644 src/utils/regex-tester.ts create mode 100644 src/utils/storage.ts create mode 100644 src/utils/text-transformer.ts create mode 100644 tests/command-translator.test.ts create mode 100644 tests/git-conflict-resolver.test.ts create mode 100644 tests/regex-tester.test.ts create mode 100644 tests/text-transformer.test.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..513ad09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +*.log +.venv/ +.verdict +.DS_Store +*.local diff --git a/icons/icon128.svg b/icons/icon128.svg new file mode 100644 index 0000000..cde5e89 --- /dev/null +++ b/icons/icon128.svg @@ -0,0 +1 @@ + diff --git a/icons/icon16.svg b/icons/icon16.svg new file mode 100644 index 0000000..66de6e4 --- /dev/null +++ b/icons/icon16.svg @@ -0,0 +1 @@ + diff --git a/icons/icon32.svg b/icons/icon32.svg new file mode 100644 index 0000000..27cef46 --- /dev/null +++ b/icons/icon32.svg @@ -0,0 +1 @@ + diff --git a/icons/icon48.svg b/icons/icon48.svg new file mode 100644 index 0000000..43e38b8 --- /dev/null +++ b/icons/icon48.svg @@ -0,0 +1 @@ + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..66e296b --- /dev/null +++ b/manifest.json @@ -0,0 +1,44 @@ +{ + "manifest_version": 3, + "name": "Screwdriver — Non-Conversational AI Tools", + "version": "1.0.0", + "description": "Dedicated, non-conversational AI interfaces for developers. Screwdrivers, not Swiss Army knives.", + "permissions": [ + "storage", + "activeTab", + "scripting", + "contextMenus", + "clipboardWrite" + ], + "host_permissions": [ + "" + ], + "action": { + "default_popup": "src/popup/popup.html", + "default_icon": { + "16": "icons/icon16.svg", + "32": "icons/icon32.svg", + "48": "icons/icon48.svg", + "128": "icons/icon128.svg" + } + }, + "background": { + "service_worker": "src/background.ts", + "type": "module" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["src/content/content.ts"], + "css": ["src/content/content.css"], + "run_at": "document_end" + } + ], + "options_page": "src/options/options.html", + "icons": { + "16": "icons/icon16.svg", + "32": "icons/icon32.svg", + "48": "icons/icon48.svg", + "128": "icons/icon128.svg" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8319ecb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2277 @@ +{ + "name": "non-conversational-ai-tool-interface", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "non-conversational-ai-tool-interface", + "version": "1.0.0", + "dependencies": { + "@webcomponents/custom-elements": "^1.6.0" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.0.0-beta.28", + "@types/chrome": "^0.0.268", + "@types/node": "^20.11.0", + "typescript": "^5.3.3", + "vite": "^5.0.12", + "vitest": "^1.2.0" + } + }, + "node_modules/@crxjs/vite-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@crxjs/vite-plugin/-/vite-plugin-2.6.1.tgz", + "integrity": "sha512-0RmYlUtQGvHXCz3B/FW7P5j+RBZUc4mk3GIRPXCx5a52N8AQ0e35GvE+Pcap/TKuI09PJgcRTdXeBOavtLVyYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webcomponents/custom-elements": "^1.5.0", + "acorn-walk": "^8.2.0", + "convert-source-map": "^1.7.0", + "debug": "^4.3.3", + "es-module-lexer": "^0.10.0", + "fs-extra": "^10.0.1", + "jsesc": "^3.0.2", + "magic-string": "^0.30.12", + "node-html-parser": "^7.0.2", + "pathe": "^2.0.1", + "picocolors": "^1.1.1", + "react-refresh": "^0.13.0", + "rollup": "2.80.0", + "rxjs": "7.5.7", + "tinyglobby": "^0.2.15" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chrome": { + "version": "0.0.268", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.268.tgz", + "integrity": "sha512-7N1QH9buudSJ7sI8Pe4mBHJr5oZ48s0hcanI9w3wgijAlv1OZNUZve9JR4x42dn5lJ5Sm87V1JNfnoh10EnQlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/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/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/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/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webcomponents/custom-elements": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz", + "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/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/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "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/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/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/es-module-lexer": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.10.5.tgz", + "integrity": "sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==", + "dev": true, + "license": "MIT" + }, + "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/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/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "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/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/node-html-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz", + "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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": "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/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.13.0.tgz", + "integrity": "sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/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/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/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/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.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/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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/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": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "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/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/vite/node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "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": "1.6.1", + "@vitest/ui": "1.6.1", + "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/vitest/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/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/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a1f1ab5 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "non-conversational-ai-tool-interface", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@webcomponents/custom-elements": "^1.6.0" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.0.0-beta.28", + "@types/chrome": "^0.0.268", + "@types/node": "^20.11.0", + "typescript": "^5.3.3", + "vite": "^5.0.12", + "vitest": "^1.2.0" + } +} diff --git a/src/background.ts b/src/background.ts new file mode 100644 index 0000000..1ca6677 --- /dev/null +++ b/src/background.ts @@ -0,0 +1,108 @@ +import { transformText, type TransformType } from './utils/text-transformer.js' +import { getSettings } from './utils/storage.js' + +chrome.runtime.onInstalled.addListener(() => { + setupContextMenus() +}) + +chrome.runtime.onStartup.addListener(() => { + setupContextMenus() +}) + +function setupContextMenus() { + chrome.contextMenus.removeAll(() => { + chrome.contextMenus.create({ + id: 'screwdriver_root', + title: 'Screwdriver', + contexts: ['selection'], + }) + + const quickTransforms: { type: TransformType; title: string }[] = [ + { type: 'base64encode', title: 'Base64 Encode' }, + { type: 'base64decode', title: 'Base64 Decode' }, + { type: 'urlescape', title: 'URL Encode' }, + { type: 'urlunescape', title: 'URL Decode' }, + { type: 'jsonpretty', title: 'Format JSON' }, + { type: 'jsoncompact', title: 'Minify JSON' }, + { type: 'camelcase', title: 'camelCase' }, + { type: 'snakecase', title: 'snake_case' }, + { type: 'kebabcase', title: 'kebab-case' }, + { type: 'uppercase', title: 'UPPERCASE' }, + { type: 'lowercase', title: 'lowercase' }, + ] + + for (const t of quickTransforms) { + chrome.contextMenus.create({ + id: `transform_${t.type}`, + parentId: 'screwdriver_root', + title: t.title, + contexts: ['selection'], + }) + } + + chrome.contextMenus.create({ + id: 'open_popup', + parentId: 'screwdriver_root', + title: 'Open Screwdriver Panel', + contexts: ['selection'], + }) + }) +} + +chrome.contextMenus.onClicked.addListener(async (info, tab) => { + if (!tab?.id) return + + if (info.menuItemId === 'open_popup') { + chrome.action.openPopup() + return + } + + if (typeof info.menuItemId === 'string' && info.menuItemId.startsWith('transform_')) { + const type = info.menuItemId.replace('transform_', '') as TransformType + const selectedText = info.selectionText ?? '' + const result = transformText(selectedText, type) + + if (result.success) { + await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: (text: string) => { + navigator.clipboard.writeText(text).catch(() => { + // Fallback for pages without clipboard permission + const el = document.createElement('textarea') + el.value = text + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) + }) + }, + args: [result.result], + }) + + chrome.notifications.create({ + type: 'basic', + iconUrl: 'icons/icon128.png', + title: 'Screwdriver', + message: `${type} result copied to clipboard`, + }) + } + } +}) + +chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { + if (request.action === 'getSettings') { + getSettings().then(sendResponse).catch(() => sendResponse(null)) + return true + } + + if (request.action === 'transform') { + const result = transformText(request.text, request.type as TransformType) + sendResponse(result) + return true + } + + if (request.action === 'ping') { + sendResponse({ ok: true }) + return true + } +}) diff --git a/src/content/content.css b/src/content/content.css new file mode 100644 index 0000000..c17e1c4 --- /dev/null +++ b/src/content/content.css @@ -0,0 +1,4 @@ +/* Screwdriver content script styles */ +#screwdriver-inline-panel textarea::placeholder { + color: #6e6e6e; +} diff --git a/src/content/content.ts b/src/content/content.ts new file mode 100644 index 0000000..a95290f --- /dev/null +++ b/src/content/content.ts @@ -0,0 +1,199 @@ +import { transformText, TRANSFORM_OPTIONS, type TransformType } from '../utils/text-transformer.js' + +let panel: HTMLElement | null = null + +function createInlinePanel() { + if (panel) return + + panel = document.createElement('div') + panel.id = 'screwdriver-inline-panel' + panel.style.cssText = ` + position: fixed; + top: 16px; + right: 16px; + width: 320px; + max-height: 80vh; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0,0,0,0.4); + z-index: 2147483647; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace; + font-size: 13px; + color: #d4d4d4; + display: flex; + flex-direction: column; + overflow: hidden; + ` + + const header = document.createElement('div') + header.style.cssText = ` + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + border-bottom: 1px solid #3c3c3c; + background: #252526; + font-weight: 600; + font-size: 12px; + color: #cccccc; + ` + header.textContent = 'Screwdriver' + + const closeBtn = document.createElement('button') + closeBtn.textContent = '×' + closeBtn.style.cssText = ` + background: none; + border: none; + color: #cccccc; + font-size: 18px; + cursor: pointer; + line-height: 1; + padding: 0 4px; + ` + closeBtn.onclick = () => { + if (panel) { + panel.remove() + panel = null + } + } + header.appendChild(closeBtn) + panel.appendChild(header) + + const content = document.createElement('div') + content.style.cssText = 'padding: 12px; overflow-y: auto;' + + const input = document.createElement('textarea') + input.placeholder = 'Paste text here...' + input.style.cssText = ` + width: 100%; + height: 80px; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #d4d4d4; + padding: 8px; + font-family: monospace; + font-size: 12px; + resize: vertical; + box-sizing: border-box; + margin-bottom: 8px; + ` + content.appendChild(input) + + const select = document.createElement('select') + select.style.cssText = ` + width: 100%; + padding: 6px; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #d4d4d4; + font-size: 12px; + margin-bottom: 8px; + ` + for (const opt of TRANSFORM_OPTIONS) { + const option = document.createElement('option') + option.value = opt.type + option.textContent = `${opt.label} — ${opt.description}` + select.appendChild(option) + } + content.appendChild(select) + + const output = document.createElement('textarea') + output.readOnly = true + output.placeholder = 'Result...' + output.style.cssText = ` + width: 100%; + height: 80px; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #9cdcfe; + padding: 8px; + font-family: monospace; + font-size: 12px; + resize: vertical; + box-sizing: border-box; + margin-bottom: 8px; + ` + content.appendChild(output) + + const btnRow = document.createElement('div') + btnRow.style.cssText = 'display: flex; gap: 8px;' + + const transformBtn = document.createElement('button') + transformBtn.textContent = 'Transform' + transformBtn.style.cssText = ` + flex: 1; + padding: 8px; + background: #0e639c; + border: none; + border-radius: 4px; + color: white; + font-size: 12px; + cursor: pointer; + ` + transformBtn.onmouseenter = () => { transformBtn.style.background = '#1177bb' } + transformBtn.onmouseleave = () => { transformBtn.style.background = '#0e639c' } + transformBtn.onclick = () => { + const result = transformText(input.value, select.value as TransformType) + if (result.success) { + output.value = result.result + output.style.borderColor = '#3c3c3c' + } else { + output.value = `Error: ${result.error ?? 'Unknown error'}` + output.style.borderColor = '#f44336' + } + } + btnRow.appendChild(transformBtn) + + const copyBtn = document.createElement('button') + copyBtn.textContent = 'Copy' + copyBtn.style.cssText = ` + padding: 8px 16px; + background: #3c3c3c; + border: none; + border-radius: 4px; + color: #cccccc; + font-size: 12px; + cursor: pointer; + ` + copyBtn.onclick = () => { + navigator.clipboard.writeText(output.value) + copyBtn.textContent = 'Copied!' + setTimeout(() => { copyBtn.textContent = 'Copy' }, 1500) + } + btnRow.appendChild(copyBtn) + + content.appendChild(btnRow) + panel.appendChild(content) + + document.body.appendChild(panel) +} + +// Keyboard shortcut: Alt+Shift+S to toggle panel +document.addEventListener('keydown', (e) => { + if (e.altKey && e.shiftKey && e.key === 'S') { + e.preventDefault() + if (panel) { + panel.remove() + panel = null + } else { + createInlinePanel() + } + } +}) + +// Listen for messages from popup/background +chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { + if (request.action === 'showInlinePanel') { + createInlinePanel() + sendResponse({ ok: true }) + return true + } + if (request.action === 'getSelectedText') { + sendResponse({ text: window.getSelection()?.toString() ?? '' }) + return true + } +}) diff --git a/src/options/options.css b/src/options/options.css new file mode 100644 index 0000000..6a632cf --- /dev/null +++ b/src/options/options.css @@ -0,0 +1,146 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 14px; + line-height: 1.5; + color: #d4d4d4; + background: #1e1e1e; +} + +.page { + max-width: 600px; + margin: 0 auto; + padding: 40px 20px; +} + +.page-header { + margin-bottom: 32px; +} + +.page-header h1 { + font-size: 24px; + font-weight: 600; + color: #cccccc; + margin-bottom: 6px; +} + +.page-header p { + color: #858585; + font-size: 14px; +} + +.settings { + display: flex; + flex-direction: column; + gap: 24px; +} + +.setting-group { + background: #252526; + border: 1px solid #3c3c3c; + border-radius: 8px; + padding: 20px; +} + +.setting-group h2 { + font-size: 14px; + font-weight: 600; + color: #cccccc; + margin-bottom: 16px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.setting { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid #3c3c3c; +} + +.setting:last-child { + border-bottom: none; +} + +.setting span { + color: #d4d4d4; + font-size: 13px; +} + +.setting input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: #0e639c; + cursor: pointer; +} + +.setting select { + padding: 6px 10px; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #d4d4d4; + font-size: 13px; + cursor: pointer; + outline: none; +} + +.setting select:focus { + border-color: #0e639c; +} + +.btn-secondary { + padding: 6px 14px; + background: #3c3c3c; + border: none; + border-radius: 4px; + color: #cccccc; + font-size: 13px; + cursor: pointer; + transition: background 0.15s; +} + +.btn-secondary:hover { + background: #4c4c4c; +} + +.btn-danger { + padding: 6px 14px; + background: #f4433620; + border: 1px solid #f44336; + border-radius: 4px; + color: #f44336; + font-size: 13px; + cursor: pointer; + transition: all 0.15s; +} + +.btn-danger:hover { + background: #f4433640; +} + +.status { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + padding: 10px 20px; + background: #0e639c; + color: white; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + transition: opacity 0.3s; +} + +.status.hidden { + opacity: 0; + pointer-events: none; +} diff --git a/src/options/options.html b/src/options/options.html new file mode 100644 index 0000000..c4840d8 --- /dev/null +++ b/src/options/options.html @@ -0,0 +1,80 @@ + + + + + + Screwdriver Settings + + + +
+ + +
+
+

General

+ + + + +
+ +
+

Shell Preferences

+ +
+ +
+

Appearance

+ +
+ +
+

Data

+
+ Clipboard history + +
+
+ Reset all settings + +
+
+ + +
+
+ + + + diff --git a/src/options/options.ts b/src/options/options.ts new file mode 100644 index 0000000..3ca7ffc --- /dev/null +++ b/src/options/options.ts @@ -0,0 +1,63 @@ +import { getSettings, setSettings, clearClipboardHistory } from '../utils/storage.js' + +const showDanger = document.getElementById('show-danger') as HTMLInputElement +const copyOnTranslate = document.getElementById('copy-on-translate') as HTMLInputElement +const contextMenu = document.getElementById('context-menu') as HTMLInputElement +const inlinePanel = document.getElementById('inline-panel') as HTMLInputElement +const preferredShell = document.getElementById('preferred-shell') as HTMLSelectElement +const theme = document.getElementById('theme') as HTMLSelectElement +const statusEl = document.getElementById('status') as HTMLElement + +async function loadSettings() { + const settings = await getSettings() + showDanger.checked = settings.showDangerWarning + copyOnTranslate.checked = settings.copyOnTranslate + contextMenu.checked = settings.contextMenuEnabled + inlinePanel.checked = settings.inlinePanelEnabled + preferredShell.value = settings.preferredShell + theme.value = settings.theme +} + +async function save() { + await setSettings({ + showDangerWarning: showDanger.checked, + copyOnTranslate: copyOnTranslate.checked, + contextMenuEnabled: contextMenu.checked, + inlinePanelEnabled: inlinePanel.checked, + preferredShell: preferredShell.value as 'bash' | 'zsh' | 'fish' | 'powershell', + theme: theme.value as 'light' | 'dark' | 'auto', + }) + statusEl.classList.remove('hidden') + setTimeout(() => statusEl.classList.add('hidden'), 2000) +} + +showDanger.addEventListener('change', save) +copyOnTranslate.addEventListener('change', save) +contextMenu.addEventListener('change', save) +inlinePanel.addEventListener('change', save) +preferredShell.addEventListener('change', save) +theme.addEventListener('change', save) + +document.getElementById('clear-history')?.addEventListener('click', async () => { + await clearClipboardHistory() + statusEl.textContent = 'History cleared' + statusEl.classList.remove('hidden') + setTimeout(() => { + statusEl.classList.add('hidden') + statusEl.textContent = 'Settings saved' + }, 2000) +}) + +document.getElementById('reset-settings')?.addEventListener('click', async () => { + if (!confirm('Reset all settings to defaults?')) return + await chrome.storage.sync.remove('screwdriver_settings') + await loadSettings() + statusEl.textContent = 'Settings reset' + statusEl.classList.remove('hidden') + setTimeout(() => { + statusEl.classList.add('hidden') + statusEl.textContent = 'Settings saved' + }, 2000) +}) + +loadSettings() diff --git a/src/popup/popup.css b/src/popup/popup.css new file mode 100644 index 0000000..fdb1897 --- /dev/null +++ b/src/popup/popup.css @@ -0,0 +1,409 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 13px; + line-height: 1.4; + color: #d4d4d4; + background: #1e1e1e; + width: 380px; + min-height: 500px; +} + +.container { + display: flex; + flex-direction: column; + height: 100vh; + max-height: 600px; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + background: #252526; + border-bottom: 1px solid #3c3c3c; +} + +.logo { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + font-size: 14px; + color: #cccccc; +} + +.logo svg { + color: #0e639c; +} + +.header-actions { + display: flex; + gap: 4px; +} + +.header-actions button { + background: none; + border: none; + color: #cccccc; + font-size: 14px; + cursor: pointer; + padding: 4px 6px; + border-radius: 4px; +} + +.header-actions button:hover { + background: #3c3c3c; +} + +.tabs { + display: flex; + background: #1e1e1e; + border-bottom: 1px solid #3c3c3c; +} + +.tab { + flex: 1; + padding: 8px; + background: none; + border: none; + color: #858585; + font-size: 12px; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: all 0.15s; +} + +.tab:hover { + color: #cccccc; +} + +.tab.active { + color: #0e639c; + border-bottom-color: #0e639c; + font-weight: 500; +} + +.tab-content { + display: none; + flex: 1; + overflow-y: auto; + padding: 12px; +} + +.tab-content.active { + display: block; +} + +.tool-section { + margin-bottom: 12px; +} + +.tool-label { + display: block; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.3px; + color: #858585; + margin-bottom: 6px; +} + +input[type="text"], +select, +textarea { + width: 100%; + padding: 8px; + background: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #d4d4d4; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; + font-size: 12px; + outline: none; +} + +input[type="text"]:focus, +select:focus, +textarea:focus { + border-color: #0e639c; +} + +textarea { + min-height: 80px; + resize: vertical; +} + +select { + cursor: pointer; +} + +.result { + background: #252526; + border: 1px solid #3c3c3c; + border-radius: 4px; + padding: 10px; + margin-top: 8px; +} + +.result-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; + font-size: 11px; + color: #858585; +} + +.result code { + display: block; + padding: 8px; + background: #1e1e1e; + border-radius: 3px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; + font-size: 12px; + color: #9cdcfe; + word-break: break-all; +} + +.result-actions { + display: flex; + gap: 8px; + margin-top: 10px; +} + +.btn-primary { + padding: 6px 14px; + background: #0e639c; + border: none; + border-radius: 4px; + color: white; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s; +} + +.btn-primary:hover { + background: #1177bb; +} + +.btn-secondary { + padding: 6px 14px; + background: #3c3c3c; + border: none; + border-radius: 4px; + color: #cccccc; + font-size: 12px; + cursor: pointer; + transition: background 0.15s; +} + +.btn-secondary:hover { + background: #4c4c4c; +} + +.btn-ghost { + padding: 6px 14px; + background: none; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #858585; + font-size: 12px; + cursor: pointer; +} + +.btn-ghost:hover { + color: #cccccc; + border-color: #4c4c4c; +} + +.badge { + padding: 2px 6px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} + +.badge.danger { + background: #f4433620; + color: #f44336; +} + +.badge.success { + background: #4caf5020; + color: #4caf50; +} + +.hidden { + display: none !important; +} + +.empty-state { + text-align: center; + padding: 20px; + color: #6e6e6e; +} + +.empty-state .hint { + font-size: 11px; + margin-top: 6px; +} + +.suggestions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} + +.suggestion-chip { + padding: 4px 10px; + background: #252526; + border: 1px solid #3c3c3c; + border-radius: 12px; + font-size: 11px; + color: #0e639c; + cursor: pointer; +} + +.suggestion-chip:hover { + border-color: #0e639c; +} + +.quick-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 6px; +} + +.quick-btn { + padding: 6px 8px; + background: #252526; + border: 1px solid #3c3c3c; + border-radius: 4px; + color: #cccccc; + font-family: 'SF Mono', Monaco, monospace; + font-size: 11px; + cursor: pointer; + text-align: left; + transition: all 0.15s; +} + +.quick-btn:hover { + border-color: #0e639c; + color: #0e639c; +} + +.flag-group { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.flag { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #cccccc; + cursor: pointer; +} + +.flag input { + accent-color: #0e639c; +} + +.regex-result { + margin-top: 10px; + padding: 10px; + background: #252526; + border: 1px solid #3c3c3c; + border-radius: 4px; +} + +.match-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px; + border-bottom: 1px solid #3c3c3c; + font-size: 12px; +} + +.match-item:last-child { + border-bottom: none; +} + +.match-index { + font-size: 10px; + color: #858585; + min-width: 24px; +} + +.match-text { + color: #9cdcfe; + font-family: monospace; + flex: 1; + word-break: break-all; +} + +.match-groups { + font-size: 10px; + color: #858585; +} + +.match-error { + color: #f44336; + font-size: 12px; + padding: 6px; +} + +.match-empty { + color: #6e6e6e; + font-size: 12px; + padding: 6px; +} + +.explanation { + margin-top: 8px; + padding: 8px; + background: #1e1e1e; + border-left: 3px solid #0e639c; + border-radius: 0 4px 4px 0; + font-size: 11px; + color: #858585; +} + +.footer { + padding: 8px 14px; + border-top: 1px solid #3c3c3c; + text-align: center; + font-size: 10px; + color: #6e6e6e; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #3c3c3c; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #4c4c4c; +} diff --git a/src/popup/popup.html b/src/popup/popup.html new file mode 100644 index 0000000..eb1f541 --- /dev/null +++ b/src/popup/popup.html @@ -0,0 +1,160 @@ + + + + + + Screwdriver + + + +
+
+ +
+ + +
+
+ + + + +
+
+ + +
+ +
+

Type a command description above

+

Try: "list files", "git log", "docker ps", "find file"

+
+
+ +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+ +
+ + +
+
+ + +
+
+ +
+ + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ Alt+Shift+S on any page +
+
+ + + + diff --git a/src/popup/popup.ts b/src/popup/popup.ts new file mode 100644 index 0000000..577299e --- /dev/null +++ b/src/popup/popup.ts @@ -0,0 +1,290 @@ +import { translateCommand } from '../utils/command-translator.js' +import { transformText, TRANSFORM_OPTIONS } from '../utils/text-transformer.js' +import { resolveConflicts } from '../utils/git-conflict-resolver.js' +import { testRegex } from '../utils/regex-tester.js' +import { addClipboardItem } from '../utils/storage.js' + +// Tab switching +document.querySelectorAll('.tab').forEach(tab => { + tab.addEventListener('click', () => { + const target = tab.getAttribute('data-tab') + if (!target) return + + document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')) + document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')) + + tab.classList.add('active') + document.getElementById(`tab-${target}`)?.classList.add('active') + }) +}) + +// Shell tab +const shellInput = document.getElementById('shell-input') as HTMLInputElement +const shellResult = document.getElementById('shell-result') as HTMLElement +const shellEmpty = document.getElementById('shell-empty') as HTMLElement +const shellDesc = document.getElementById('shell-desc') as HTMLElement +const shellDanger = document.getElementById('shell-danger') as HTMLElement +const shellCommand = document.getElementById('shell-command') as HTMLElement +const shellSuggestions = document.getElementById('shell-suggestions') as HTMLElement + +shellInput.addEventListener('input', () => { + const result = translateCommand(shellInput.value) + shellSuggestions.innerHTML = '' + + if (result.success && result.command) { + shellEmpty.classList.add('hidden') + shellResult.classList.remove('hidden') + shellDesc.textContent = result.description ?? '' + shellCommand.textContent = result.command + shellDanger.classList.toggle('hidden', !result.dangerous) + } else { + shellResult.classList.add('hidden') + shellEmpty.classList.remove('hidden') + + if (result.suggestions) { + for (const s of result.suggestions) { + const chip = document.createElement('span') + chip.className = 'suggestion-chip' + chip.textContent = s + chip.addEventListener('click', () => { + shellInput.value = s + shellInput.dispatchEvent(new Event('input')) + }) + shellSuggestions.appendChild(chip) + } + } + } +}) + +document.getElementById('shell-copy')?.addEventListener('click', async () => { + const cmd = shellCommand.textContent ?? '' + await navigator.clipboard.writeText(cmd) + await addClipboardItem({ text: cmd, type: 'command' }) + const btn = document.getElementById('shell-copy') as HTMLButtonElement + btn.textContent = 'Copied!' + setTimeout(() => { btn.textContent = 'Copy' }, 1500) +}) + +document.getElementById('shell-explain')?.addEventListener('click', () => { + const cmd = shellCommand.textContent ?? '' + const parts = cmd.split(' ') + const explanations: string[] = [] + const cmdExplanations: Record = { + 'ls': 'List directory contents', + 'cd': 'Change directory', + 'pwd': 'Print working directory', + 'cat': 'Display file contents', + 'rm': 'Remove files/directories', + 'cp': 'Copy files/directories', + 'mv': 'Move/rename files', + 'mkdir': 'Create directory', + 'touch': 'Create empty file or update timestamp', + 'grep': 'Search text using patterns', + 'find': 'Search for files', + 'ps': 'Report process status', + 'kill': 'Send signal to process', + 'df': 'Report disk space', + 'du': 'Estimate file space usage', + 'tar': 'Archive files', + 'chmod': 'Change file permissions', + 'chown': 'Change file owner', + 'git': 'Version control system', + 'docker': 'Container management', + 'npm': 'Node package manager', + 'curl': 'Transfer data from URL', + 'ping': 'Test network connectivity', + 'env': 'Show environment variables', + 'whoami': 'Show current user', + 'date': 'Show current date/time', + 'lsof': 'List open files', + } + for (const part of parts) { + if (cmdExplanations[part]) { + explanations.push(`${part}: ${cmdExplanations[part]}`) + } + } + if (explanations.length > 0) { + alert(explanations.join('\n')) + } +}) + +// Quick commands +const quickGrid = document.getElementById('quick-commands') as HTMLElement +const quickCommands = [ + 'git status', 'git log', 'git add .', 'git commit', + 'ls -la', 'pwd', 'docker ps', 'npm test', +] +for (const cmd of quickCommands) { + const btn = document.createElement('button') + btn.className = 'quick-btn' + btn.textContent = cmd + btn.addEventListener('click', () => { + shellInput.value = cmd + shellInput.dispatchEvent(new Event('input')) + }) + quickGrid.appendChild(btn) +} + +// Transform tab +const transformInput = document.getElementById('transform-input') as HTMLTextAreaElement +const transformSelect = document.getElementById('transform-select') as HTMLSelectElement +const transformOutput = document.getElementById('transform-output') as HTMLTextAreaElement + +for (const opt of TRANSFORM_OPTIONS) { + const option = document.createElement('option') + option.value = opt.type + option.textContent = `${opt.label} — ${opt.description}` + transformSelect.appendChild(option) +} + +document.getElementById('transform-run')?.addEventListener('click', () => { + const result = transformText(transformInput.value, transformSelect.value as never) + if (result.success) { + transformOutput.value = result.result + transformOutput.style.borderColor = '' + } else { + transformOutput.value = `Error: ${result.error ?? 'Unknown'}` + transformOutput.style.borderColor = '#f44336' + } +}) + +document.getElementById('transform-copy')?.addEventListener('click', async () => { + await navigator.clipboard.writeText(transformOutput.value) + await addClipboardItem({ text: transformOutput.value, type: 'transform' }) + const btn = document.getElementById('transform-copy') as HTMLButtonElement + btn.textContent = 'Copied!' + setTimeout(() => { btn.textContent = 'Copy' }, 1500) +}) + +document.getElementById('transform-clear')?.addEventListener('click', () => { + transformInput.value = '' + transformOutput.value = '' + transformOutput.style.borderColor = '' +}) + +// Git tab +const gitInput = document.getElementById('git-input') as HTMLTextAreaElement +const gitStrategy = document.getElementById('git-strategy') as HTMLSelectElement +const gitOutput = document.getElementById('git-output') as HTMLTextAreaElement +const gitExplanation = document.getElementById('git-explanation') as HTMLElement + +document.getElementById('git-resolve')?.addEventListener('click', () => { + const strategy = gitStrategy.value as 'ours' | 'theirs' | 'both' | 'manual' | '' + const result = resolveConflicts(gitInput.value, strategy || undefined) + + if (result.success && result.resolution) { + gitOutput.value = result.resolution + gitOutput.style.borderColor = '' + gitExplanation.textContent = result.explanation + gitExplanation.classList.remove('hidden') + } else { + gitOutput.value = result.explanation + gitOutput.style.borderColor = '#f44336' + gitExplanation.classList.add('hidden') + } +}) + +document.getElementById('git-copy')?.addEventListener('click', async () => { + await navigator.clipboard.writeText(gitOutput.value) + await addClipboardItem({ text: gitOutput.value, type: 'conflict' }) + const btn = document.getElementById('git-copy') as HTMLButtonElement + btn.textContent = 'Copied!' + setTimeout(() => { btn.textContent = 'Copy' }, 1500) +}) + +document.getElementById('git-clear')?.addEventListener('click', () => { + gitInput.value = '' + gitOutput.value = '' + gitExplanation.classList.add('hidden') + gitOutput.style.borderColor = '' +}) + +// Regex tab +const regexPattern = document.getElementById('regex-pattern') as HTMLInputElement +const regexText = document.getElementById('regex-text') as HTMLTextAreaElement +const regexReplace = document.getElementById('regex-replace') as HTMLInputElement +const regexResult = document.getElementById('regex-result') as HTMLElement +const regexStatus = document.getElementById('regex-status') as HTMLElement +const regexCount = document.getElementById('regex-count') as HTMLElement +const regexMatches = document.getElementById('regex-matches') as HTMLElement +const regexReplacePreview = document.getElementById('regex-replace-preview') as HTMLElement +const regexReplaceOutput = document.getElementById('regex-replace-output') as HTMLElement + +document.getElementById('regex-test')?.addEventListener('click', () => { + const flags = [ + (document.getElementById('flag-g') as HTMLInputElement).checked ? 'g' : '', + (document.getElementById('flag-i') as HTMLInputElement).checked ? 'i' : '', + (document.getElementById('flag-m') as HTMLInputElement).checked ? 'm' : '', + (document.getElementById('flag-s') as HTMLInputElement).checked ? 's' : '', + ].join('') + + const result = testRegex({ + pattern: regexPattern.value, + flags, + text: regexText.value, + replacement: regexReplace.value || undefined, + }) + + regexResult.classList.remove('hidden') + + if (!result.valid) { + regexStatus.textContent = 'Invalid regex' + regexStatus.className = 'badge danger' + regexCount.textContent = '' + regexMatches.innerHTML = `
${result.error ?? ''}
` + regexReplacePreview.classList.add('hidden') + return + } + + regexStatus.textContent = 'Valid regex' + regexStatus.className = 'badge success' + regexCount.textContent = `${result.matches.length} match${result.matches.length !== 1 ? 'es' : ''}` + + if (result.matches.length === 0) { + regexMatches.innerHTML = '
No matches found
' + } else { + regexMatches.innerHTML = result.matches.map((m, i) => ` +
+ #${i + 1} + ${escapeHtml(m.full)} + ${m.groups.length > 0 ? `Groups: ${m.groups.map((g, j) => `${j + 1}=${escapeHtml(g ?? '')}`).join(', ')}` : ''} +
+ `).join('') + } + + if (result.replacePreview !== undefined) { + regexReplaceOutput.textContent = result.replacePreview + regexReplacePreview.classList.remove('hidden') + } else { + regexReplacePreview.classList.add('hidden') + } +}) + +document.getElementById('regex-clear')?.addEventListener('click', () => { + regexPattern.value = '' + regexText.value = '' + regexReplace.value = '' + regexResult.classList.add('hidden') + ;(document.getElementById('flag-g') as HTMLInputElement).checked = true + ;(document.getElementById('flag-i') as HTMLInputElement).checked = false + ;(document.getElementById('flag-m') as HTMLInputElement).checked = false + ;(document.getElementById('flag-s') as HTMLInputElement).checked = false +}) + +function escapeHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +// Settings button +document.getElementById('btn-settings')?.addEventListener('click', () => { + chrome.runtime.openOptionsPage() +}) + +// Theme toggle +document.getElementById('btn-theme')?.addEventListener('click', () => { + document.body.classList.toggle('dark') +}) diff --git a/src/utils/command-translator.ts b/src/utils/command-translator.ts new file mode 100644 index 0000000..00a0fdb --- /dev/null +++ b/src/utils/command-translator.ts @@ -0,0 +1,356 @@ +export interface CommandPattern { + readonly patterns: readonly string[] + readonly command: string + readonly description: string + readonly dangerous: boolean +} + +const COMMAND_PATTERNS: readonly CommandPattern[] = [ + // Navigation + { + patterns: ['list files', 'show files', 'ls', 'what files', 'dir'], + command: 'ls -la', + description: 'List all files in current directory', + dangerous: false, + }, + { + patterns: ['list directory', 'show directory', 'current directory', 'pwd'], + command: 'pwd', + description: 'Print working directory', + dangerous: false, + }, + { + patterns: ['go up', 'parent directory', 'cd up', 'up one level'], + command: 'cd ..', + description: 'Go to parent directory', + dangerous: false, + }, + { + patterns: ['go home', 'home directory', 'cd home'], + command: 'cd ~', + description: 'Go to home directory', + dangerous: false, + }, + // File operations + { + patterns: ['create file', 'new file', 'touch file', 'make file'], + command: 'touch filename', + description: 'Create empty file (replace "filename")', + dangerous: false, + }, + { + patterns: ['create directory', 'new folder', 'mkdir', 'make directory'], + command: 'mkdir dirname', + description: 'Create directory (replace "dirname")', + dangerous: false, + }, + { + patterns: ['remove file', 'delete file', 'rm file'], + command: 'rm filename', + description: 'Remove file (replace "filename") ⚠️', + dangerous: true, + }, + { + patterns: ['remove directory', 'delete folder', 'rm dir', 'rmdir'], + command: 'rm -r dirname', + description: 'Remove directory recursively (replace "dirname") ⚠️', + dangerous: true, + }, + { + patterns: ['copy file', 'cp file'], + command: 'cp source dest', + description: 'Copy file (replace "source" and "dest")', + dangerous: false, + }, + { + patterns: ['move file', 'mv file', 'rename file'], + command: 'mv source dest', + description: 'Move/rename file (replace "source" and "dest")', + dangerous: false, + }, + // Git + { + patterns: ['git status', 'check git', 'git changes'], + command: 'git status', + description: 'Show git working tree status', + dangerous: false, + }, + { + patterns: ['git log', 'show commits', 'view history'], + command: 'git log --oneline -n 20', + description: 'Show recent commits', + dangerous: false, + }, + { + patterns: ['git add', 'stage files', 'stage changes'], + command: 'git add .', + description: 'Stage all changes ⚠️', + dangerous: true, + }, + { + patterns: ['git commit', 'commit changes'], + command: 'git commit -m "message"', + description: 'Commit staged changes (replace "message")', + dangerous: false, + }, + { + patterns: ['git push', 'push changes'], + command: 'git push', + description: 'Push commits to remote ⚠️', + dangerous: true, + }, + { + patterns: ['git pull', 'pull changes'], + command: 'git pull', + description: 'Pull changes from remote ⚠️', + dangerous: true, + }, + { + patterns: ['git branch', 'list branches', 'show branches'], + command: 'git branch -a', + description: 'List all branches', + dangerous: false, + }, + { + patterns: ['git checkout', 'switch branch'], + command: 'git checkout branchname', + description: 'Switch branch (replace "branchname")', + dangerous: false, + }, + { + patterns: ['git stash', 'stash changes'], + command: 'git stash', + description: 'Stash current changes', + dangerous: false, + }, + { + patterns: ['git diff', 'show diff', 'view changes'], + command: 'git diff', + description: 'Show unstaged changes', + dangerous: false, + }, + // Search + { + patterns: ['find file', 'search file', 'locate file'], + command: 'find . -name "pattern"', + description: 'Find files by name (replace "pattern")', + dangerous: false, + }, + { + patterns: ['grep', 'search text', 'find text'], + command: 'grep -r "pattern" .', + description: 'Search text recursively (replace "pattern")', + dangerous: false, + }, + { + patterns: ['search content', 'find in files'], + command: 'grep -rn "pattern" .', + description: 'Search with line numbers (replace "pattern")', + dangerous: false, + }, + // Process management + { + patterns: ['list processes', 'show processes', 'ps'], + command: 'ps aux', + description: 'List all running processes', + dangerous: false, + }, + { + patterns: ['kill process', 'stop process'], + command: 'kill -9 PID', + description: 'Force kill process (replace "PID") ⚠️', + dangerous: true, + }, + { + patterns: ['port usage', 'check port', 'what port'], + command: 'lsof -i :PORT', + description: 'Check what is using port (replace "PORT")', + dangerous: false, + }, + // Network + { + patterns: ['curl', 'http request', 'fetch url'], + command: 'curl -s https://example.com', + description: 'Make HTTP GET request (replace URL)', + dangerous: false, + }, + { + patterns: ['ping', 'check connection'], + command: 'ping -c 4 example.com', + description: 'Ping host (replace "example.com")', + dangerous: false, + }, + // Disk + { + patterns: ['disk space', 'free space', 'df'], + command: 'df -h', + description: 'Show disk space usage', + dangerous: false, + }, + { + patterns: ['directory size', 'folder size', 'du'], + command: 'du -sh .', + description: 'Show current directory size', + dangerous: false, + }, + // npm/yarn/pnpm + { + patterns: ['install packages', 'npm install', 'install deps'], + command: 'npm install', + description: 'Install npm dependencies', + dangerous: false, + }, + { + patterns: ['run dev', 'start dev', 'npm run dev'], + command: 'npm run dev', + description: 'Start development server', + dangerous: false, + }, + { + patterns: ['run build', 'build project', 'npm run build'], + command: 'npm run build', + description: 'Build project', + dangerous: false, + }, + { + patterns: ['run tests', 'npm test', 'test project'], + command: 'npm test', + description: 'Run tests', + dangerous: false, + }, + // Docker + { + patterns: ['docker ps', 'list containers', 'show containers'], + command: 'docker ps -a', + description: 'List all Docker containers', + dangerous: false, + }, + { + patterns: ['docker images', 'list images'], + command: 'docker images', + description: 'List Docker images', + dangerous: false, + }, + { + patterns: ['docker logs', 'container logs'], + command: 'docker logs container_name', + description: 'View container logs (replace "container_name")', + dangerous: false, + }, + // Compression + { + patterns: ['tar gzip', 'compress folder', 'archive directory'], + command: 'tar -czvf archive.tar.gz dirname', + description: 'Compress directory to tar.gz (replace names)', + dangerous: false, + }, + { + patterns: ['extract tar', 'untar', 'decompress'], + command: 'tar -xzvf archive.tar.gz', + description: 'Extract tar.gz archive (replace "archive.tar.gz")', + dangerous: false, + }, + // Permissions + { + patterns: ['chmod', 'change permissions', 'make executable'], + command: 'chmod +x filename', + description: 'Make file executable (replace "filename")', + dangerous: false, + }, + { + patterns: ['chown', 'change owner'], + command: 'chown user:group filename', + description: 'Change file owner (replace "user", "group", "filename")', + dangerous: true, + }, + // Misc + { + patterns: ['current date', 'show date', 'what time'], + command: 'date', + description: 'Show current date and time', + dangerous: false, + }, + { + patterns: ['environment variables', 'show env', 'print env'], + command: 'env', + description: 'Show environment variables', + dangerous: false, + }, + { + patterns: ['who am i', 'current user'], + command: 'whoami', + description: 'Show current user', + dangerous: false, + }, +] + +export interface TranslationResult { + readonly success: boolean + readonly command?: string + readonly description?: string + readonly dangerous?: boolean + readonly suggestions?: readonly string[] + readonly error?: string +} + +function normalizeInput(input: string): string { + return input.toLowerCase().trim().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ') +} + +function calculateScore(input: string, patterns: readonly string[]): number { + const words = new Set(input.split(' ')) + let bestScore = 0 + for (const pattern of patterns) { + const patternWords = pattern.split(' ') + const matches = patternWords.filter(w => words.has(w)).length + const score = matches / Math.max(patternWords.length, words.size) + if (score > bestScore) bestScore = score + } + return bestScore +} + +export function translateCommand(input: string): TranslationResult { + const normalized = normalizeInput(input) + if (!normalized) { + return { success: false, error: 'Empty input' } + } + + let bestMatch: CommandPattern | null = null + let bestScore = 0 + + for (const pattern of COMMAND_PATTERNS) { + const score = calculateScore(normalized, pattern.patterns) + if (score > bestScore) { + bestScore = score + bestMatch = pattern + } + } + + if (bestMatch && bestScore >= 0.3) { + return { + success: true, + command: bestMatch.command, + description: bestMatch.description, + dangerous: bestMatch.dangerous, + suggestions: bestMatch.patterns.slice(0, 3), + } + } + + // Try to find related suggestions + const related = COMMAND_PATTERNS + .map(p => ({ pattern: p, score: calculateScore(normalized, p.patterns) })) + .filter(r => r.score > 0.1) + .sort((a, b) => b.score - a.score) + .slice(0, 5) + .map(r => r.pattern.patterns[0]) + + return { + success: false, + error: 'No exact match found', + suggestions: related.length > 0 ? related : undefined, + } +} + +export function getAllCommands(): readonly CommandPattern[] { + return COMMAND_PATTERNS +} diff --git a/src/utils/git-conflict-resolver.ts b/src/utils/git-conflict-resolver.ts new file mode 100644 index 0000000..9c62bf1 --- /dev/null +++ b/src/utils/git-conflict-resolver.ts @@ -0,0 +1,159 @@ +export interface ConflictBlock { + readonly ours: string + readonly theirs: string + readonly header: string +} + +export interface ResolutionResult { + readonly success: boolean + readonly resolution?: string + readonly strategy?: 'ours' | 'theirs' | 'both' | 'manual' + readonly explanation: string + readonly conflictCount: number +} + +const CONFLICT_START = '<<<<<<< ' +const CONFLICT_DIVIDER = '=======' +const CONFLICT_END = '>>>>>>> ' + +export function parseConflicts(input: string): ConflictBlock[] { + const lines = input.split('\n') + const conflicts: ConflictBlock[] = [] + let i = 0 + + while (i < lines.length) { + if (lines[i].startsWith(CONFLICT_START)) { + const header = lines[i].slice(CONFLICT_START.length) + const oursLines: string[] = [] + i++ + while (i < lines.length && !lines[i].startsWith(CONFLICT_DIVIDER)) { + oursLines.push(lines[i]) + i++ + } + i++ // skip divider + const theirsLines: string[] = [] + while (i < lines.length && !lines[i].startsWith(CONFLICT_END)) { + theirsLines.push(lines[i]) + i++ + } + i++ // skip end marker + conflicts.push({ + ours: oursLines.join('\n'), + theirs: theirsLines.join('\n'), + header, + }) + } else { + i++ + } + } + + return conflicts +} + +function detectStrategy(ours: string, theirs: string): 'ours' | 'theirs' | 'both' | 'manual' { + const ourLines = ours.trim().split('\n').filter(l => l.trim()) + const theirLines = theirs.trim().split('\n').filter(l => l.trim()) + + // If one side is empty, take the other + if (ourLines.length === 0) return 'theirs' + if (theirLines.length === 0) return 'ours' + + // If they're identical + if (ours.trim() === theirs.trim()) return 'ours' + + // If one is a subset/superset of the other + if (ours.includes(theirs.trim()) && theirs.trim().length > 0) return 'ours' + if (theirs.includes(ours.trim()) && ours.trim().length > 0) return 'theirs' + + // Check for import/dependency additions (common pattern) + const ourImports = ourLines.filter(l => l.trim().startsWith('import ')) + const theirImports = theirLines.filter(l => l.trim().startsWith('import ')) + if (ourImports.length > 0 || theirImports.length > 0) { + return 'both' + } + + return 'manual' +} + +function resolveBlock(block: ConflictBlock, strategy: 'ours' | 'theirs' | 'both' | 'manual'): string { + switch (strategy) { + case 'ours': + return block.ours + case 'theirs': + return block.theirs + case 'both': + // Merge non-duplicate lines + const ourLines = block.ours.split('\n') + const theirLines = block.theirs.split('\n') + const merged = new Set() + const result: string[] = [] + for (const line of [...ourLines, ...theirLines]) { + const trimmed = line.trim() + if (!trimmed || merged.has(trimmed)) continue + merged.add(trimmed) + result.push(line) + } + return result.join('\n') + case 'manual': + return `/* CONFLICT: ${block.header} */\n/* YOURS */\n${block.ours}\n/* THEIRS */\n${block.theirs}\n/* END CONFLICT */` + } +} + +export function resolveConflicts( + input: string, + strategy?: 'ours' | 'theirs' | 'both' | 'manual' +): ResolutionResult { + const conflicts = parseConflicts(input) + + if (conflicts.length === 0) { + return { + success: false, + explanation: 'No git conflict markers found in the input.', + conflictCount: 0, + } + } + + const lines = input.split('\n') + const result: string[] = [] + let i = 0 + let conflictIndex = 0 + let autoResolved = 0 + let manualNeeded = 0 + + while (i < lines.length) { + if (lines[i].startsWith(CONFLICT_START)) { + const block = conflicts[conflictIndex] + const detected = strategy || detectStrategy(block.ours, block.theirs) + const resolved = resolveBlock(block, detected) + result.push(resolved) + + if (detected === 'manual') manualNeeded++ + else autoResolved++ + + // Skip past conflict markers + while (i < lines.length && !lines[i].startsWith(CONFLICT_END)) { + i++ + } + i++ // skip end marker + conflictIndex++ + } else { + result.push(lines[i]) + i++ + } + } + + const usedStrategy = strategy || detectStrategy(conflicts[0].ours, conflicts[0].theirs) + + return { + success: true, + resolution: result.join('\n'), + strategy: usedStrategy, + conflictCount: conflicts.length, + explanation: + autoResolved > 0 && manualNeeded > 0 + ? `Auto-resolved ${autoResolved} of ${conflicts.length} conflicts. ${manualNeeded} need manual review.` + : manualNeeded > 0 + ? `All ${conflicts.length} conflicts need manual review.` + : `All ${conflicts.length} conflicts auto-resolved using '${usedStrategy}' strategy.`, + } +} diff --git a/src/utils/regex-tester.ts b/src/utils/regex-tester.ts new file mode 100644 index 0000000..44a80e5 --- /dev/null +++ b/src/utils/regex-tester.ts @@ -0,0 +1,90 @@ +export interface RegexTestResult { + readonly valid: boolean + readonly matches: readonly RegexMatch[] + readonly error?: string + readonly replacePreview?: string +} + +export interface RegexMatch { + readonly full: string + readonly groups: readonly (string | undefined)[] + readonly index: number +} + +export interface RegexTestInput { + readonly pattern: string + readonly flags: string + readonly text: string + readonly replacement?: string +} + +export function testRegex(input: RegexTestInput): RegexTestResult { + try { + const regex = new RegExp(input.pattern, input.flags) + const matches: RegexMatch[] = [] + let match: RegExpExecArray | null + + if (input.flags.includes('g')) { + while ((match = regex.exec(input.text)) !== null) { + if (match.index === regex.lastIndex) regex.lastIndex++ + matches.push({ + full: match[0], + groups: match.slice(1), + index: match.index, + }) + } + } else { + match = regex.exec(input.text) + if (match) { + matches.push({ + full: match[0], + groups: match.slice(1), + index: match.index, + }) + } + } + + let replacePreview: string | undefined + if (input.replacement !== undefined) { + replacePreview = input.text.replace(regex, input.replacement) + } + + return { valid: true, matches, replacePreview } + } catch (err) { + return { + valid: false, + matches: [], + error: err instanceof Error ? err.message : 'Invalid regex', + } + } +} + +export function explainRegex(pattern: string): string { + const explanations: Record = { + '^': 'Start of string', + '$': 'End of string', + '.': 'Any character except newline', + '\\d': 'Digit (0-9)', + '\\D': 'Non-digit', + '\\w': 'Word character (a-z, A-Z, 0-9, _)', + '\\W': 'Non-word character', + '\\s': 'Whitespace', + '\\S': 'Non-whitespace', + '\\n': 'Newline', + '\\t': 'Tab', + '\\b': 'Word boundary', + '+': 'One or more of preceding', + '*': 'Zero or more of preceding', + '?': 'Zero or one of preceding', + } + + const parts: string[] = [] + for (const [token, meaning] of Object.entries(explanations)) { + if (pattern.includes(token)) { + parts.push(`${token} = ${meaning}`) + } + } + + if (parts.length === 0) return 'Pattern found' + return parts.join('; ') +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..0a66566 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,61 @@ +export interface ExtensionSettings { + showDangerWarning: boolean + copyOnTranslate: boolean + preferredShell: 'bash' | 'zsh' | 'fish' | 'powershell' + theme: 'light' | 'dark' | 'auto' + contextMenuEnabled: boolean + inlinePanelEnabled: boolean +} + +const DEFAULT_SETTINGS: ExtensionSettings = { + showDangerWarning: true, + copyOnTranslate: true, + preferredShell: 'bash', + theme: 'auto', + contextMenuEnabled: true, + inlinePanelEnabled: true, +} + +export async function getSettings(): Promise { + try { + const result = await chrome.storage.sync.get('screwdriver_settings') + return { ...DEFAULT_SETTINGS, ...(result.screwdriver_settings ?? {}) } + } catch { + return DEFAULT_SETTINGS + } +} + +export async function setSettings(settings: Partial): Promise { + const current = await getSettings() + await chrome.storage.sync.set({ + screwdriver_settings: { ...current, ...settings }, + }) +} + +export interface ClipboardItem { + id: string + text: string + timestamp: number + type: 'command' | 'transform' | 'conflict' | 'regex' +} + +export async function addClipboardItem(item: Omit): Promise { + const result = await chrome.storage.local.get('screwdriver_history') + const history: ClipboardItem[] = result.screwdriver_history ?? [] + const newItem: ClipboardItem = { + ...item, + id: `${Date.now()}_${Math.random().toString(36).slice(2)}`, + timestamp: Date.now(), + } + const updated = [newItem, ...history].slice(0, 50) + await chrome.storage.local.set({ screwdriver_history: updated }) +} + +export async function getClipboardHistory(): Promise { + const result = await chrome.storage.local.get('screwdriver_history') + return result.screwdriver_history ?? [] +} + +export async function clearClipboardHistory(): Promise { + await chrome.storage.local.remove('screwdriver_history') +} diff --git a/src/utils/text-transformer.ts b/src/utils/text-transformer.ts new file mode 100644 index 0000000..6af5db9 --- /dev/null +++ b/src/utils/text-transformer.ts @@ -0,0 +1,197 @@ +export type TransformType = + | 'uppercase' + | 'lowercase' + | 'titlecase' + | 'camelcase' + | 'pascalcase' + | 'snakecase' + | 'kebabcase' + | 'constantcase' + | 'base64encode' + | 'base64decode' + | 'urlescape' + | 'urlunescape' + | 'trim' + | 'trimlines' + | 'dedupe' + | 'sortlines' + | 'reverselines' + | 'jsonpretty' + | 'jsoncompact' + | 'jsontocsv' + | 'csvtojson' + +export interface TransformResult { + readonly success: boolean + readonly result: string + readonly error?: string +} + +export interface TransformOption { + readonly type: TransformType + readonly label: string + readonly description: string +} + +export const TRANSFORM_OPTIONS: readonly TransformOption[] = [ + { type: 'uppercase', label: 'UPPERCASE', description: 'Convert to uppercase' }, + { type: 'lowercase', label: 'lowercase', description: 'Convert to lowercase' }, + { type: 'titlecase', label: 'Title Case', description: 'Capitalize Each Word' }, + { type: 'camelcase', label: 'camelCase', description: 'camelCaseNotation' }, + { type: 'pascalcase', label: 'PascalCase', description: 'PascalCaseNotation' }, + { type: 'snakecase', label: 'snake_case', description: 'snake_case_notation' }, + { type: 'kebabcase', label: 'kebab-case', description: 'kebab-case-notation' }, + { type: 'constantcase', label: 'CONSTANT_CASE', description: 'CONSTANT_CASE_NOTATION' }, + { type: 'base64encode', label: 'Base64 Encode', description: 'Encode to Base64' }, + { type: 'base64decode', label: 'Base64 Decode', description: 'Decode from Base64' }, + { type: 'urlescape', label: 'URL Encode', description: 'Percent-encode string' }, + { type: 'urlunescape', label: 'URL Decode', description: 'Percent-decode string' }, + { type: 'trim', label: 'Trim', description: 'Remove leading/trailing whitespace' }, + { type: 'trimlines', label: 'Trim Lines', description: 'Remove leading/trailing whitespace from each line' }, + { type: 'dedupe', label: 'Remove Duplicates', description: 'Remove duplicate lines' }, + { type: 'sortlines', label: 'Sort Lines', description: 'Sort lines alphabetically' }, + { type: 'reverselines', label: 'Reverse Lines', description: 'Reverse line order' }, + { type: 'jsonpretty', label: 'JSON Pretty', description: 'Format JSON with indentation' }, + { type: 'jsoncompact', label: 'JSON Compact', description: 'Minify JSON' }, +] + +function toTitleCase(str: string): string { + return str.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase()) +} + +function toCamelCase(str: string): string { + return str + .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()) + .replace(/^[A-Z]/, chr => chr.toLowerCase()) +} + +function toPascalCase(str: string): string { + const camel = toCamelCase(str) + return camel.charAt(0).toUpperCase() + camel.slice(1) +} + +function toSnakeCase(str: string): string { + return str + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, '') + .replace(/_+/g, '_') +} + +function toKebabCase(str: string): string { + return toSnakeCase(str).replace(/_/g, '-') +} + +function toConstantCase(str: string): string { + return toSnakeCase(str).toUpperCase() +} + +export function transformText(text: string, type: TransformType): TransformResult { + try { + switch (type) { + case 'uppercase': + return { success: true, result: text.toUpperCase() } + case 'lowercase': + return { success: true, result: text.toLowerCase() } + case 'titlecase': + return { success: true, result: toTitleCase(text) } + case 'camelcase': + return { success: true, result: toCamelCase(text) } + case 'pascalcase': + return { success: true, result: toPascalCase(text) } + case 'snakecase': + return { success: true, result: toSnakeCase(text) } + case 'kebabcase': + return { success: true, result: toKebabCase(text) } + case 'constantcase': + return { success: true, result: toConstantCase(text) } + case 'base64encode': + return { success: true, result: btoa(unescape(encodeURIComponent(text))) } + case 'base64decode': { + const decoded = decodeURIComponent(escape(atob(text))) + return { success: true, result: decoded } + } + case 'urlescape': + return { success: true, result: encodeURIComponent(text) } + case 'urlunescape': + return { success: true, result: decodeURIComponent(text) } + case 'trim': + return { success: true, result: text.trim() } + case 'trimlines': + return { success: true, result: text.split('\n').map(l => l.trim()).join('\n') } + case 'dedupe': { + const seen = new Set() + return { success: true, result: text.split('\n').filter(l => { const k = l.trim(); if (!k || seen.has(k)) return false; seen.add(k); return true }).join('\n') } + } + case 'sortlines': + return { success: true, result: text.split('\n').sort().join('\n') } + case 'reverselines': + return { success: true, result: text.split('\n').reverse().join('\n') } + case 'jsonpretty': { + const parsed = JSON.parse(text) + return { success: true, result: JSON.stringify(parsed, null, 2) } + } + case 'jsoncompact': { + const parsed = JSON.parse(text) + return { success: true, result: JSON.stringify(parsed) } + } + case 'jsontocsv': { + const parsed = JSON.parse(text) + if (!Array.isArray(parsed) || parsed.length === 0) { + return { success: false, result: '', error: 'JSON must be a non-empty array of objects' } + } + const keys = Object.keys(parsed[0]) + const header = keys.join(',') + const rows = parsed.map((row: Record) => + keys.map(k => { + const val = String(row[k] ?? '') + if (val.includes(',') || val.includes('"') || val.includes('\n')) { + return `"${val.replace(/"/g, '""')}"` + } + return val + }).join(',') + ) + return { success: true, result: [header, ...rows].join('\n') } + } + case 'csvtojson': { + const lines = text.split('\n').filter(l => l.trim()) + if (lines.length < 2) { + return { success: false, result: '', error: 'CSV must have header + at least one data row' } + } + const parseCSVLine = (line: string): string[] => { + const result: string[] = [] + let current = '' + let inQuotes = false + for (let i = 0; i < line.length; i++) { + const char = line[i] + if (char === '"') { + if (inQuotes && line[i + 1] === '"') { + current += '"' + i++ + } else { + inQuotes = !inQuotes + } + } else if (char === ',' && !inQuotes) { + result.push(current.trim()) + current = '' + } else { + current += char + } + } + result.push(current.trim()) + return result + } + const headers = parseCSVLine(lines[0]) + const data = lines.slice(1).map(line => { + const values = parseCSVLine(line) + const obj: Record = {} + headers.forEach((h, i) => { obj[h] = values[i] ?? '' }) + return obj + }) + return { success: true, result: JSON.stringify(data, null, 2) } + } + } + } catch (err) { + return { success: false, result: '', error: err instanceof Error ? err.message : 'Unknown error' } + } +} diff --git a/tests/command-translator.test.ts b/tests/command-translator.test.ts new file mode 100644 index 0000000..1be0449 --- /dev/null +++ b/tests/command-translator.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest' +import { translateCommand, getAllCommands } from '../src/utils/command-translator.js' + +describe('command-translator', () => { + it('translates simple commands', () => { + const r = translateCommand('list files') + expect(r.success).toBe(true) + expect(r.command).toBe('ls -la') + }) + + it('translates git commands', () => { + const r = translateCommand('git status') + expect(r.success).toBe(true) + expect(r.command).toBe('git status') + }) + + it('marks dangerous commands', () => { + const r = translateCommand('remove directory') + expect(r.success).toBe(true) + expect(r.dangerous).toBe(true) + }) + + it('handles unknown input gracefully', () => { + const r = translateCommand('xyz') + expect(r.success).toBe(false) + }) + + it('handles empty input', () => { + const r = translateCommand('') + expect(r.success).toBe(false) + expect(r.error).toBe('Empty input') + }) + + it('has a command catalog', () => { + const cmds = getAllCommands() + expect(cmds.length).toBeGreaterThan(20) + }) +}) diff --git a/tests/git-conflict-resolver.test.ts b/tests/git-conflict-resolver.test.ts new file mode 100644 index 0000000..88d8a2f --- /dev/null +++ b/tests/git-conflict-resolver.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest' +import { parseConflicts, resolveConflicts } from '../src/utils/git-conflict-resolver.js' + +const CONFLICT_TEXT = `import { foo } from './foo' +<<<<<<< HEAD +const x = 1 +======= +const x = 2 +>>>>>>> feature +export { x }` + +describe('git-conflict-resolver', () => { + it('parses conflicts', () => { + const conflicts = parseConflicts(CONFLICT_TEXT) + expect(conflicts.length).toBe(1) + expect(conflicts[0].ours).toBe('const x = 1') + expect(conflicts[0].theirs).toBe('const x = 2') + expect(conflicts[0].header).toBe('HEAD') + }) + + it('auto-resolves with ours strategy', () => { + const result = resolveConflicts(CONFLICT_TEXT, 'ours') + expect(result.success).toBe(true) + expect(result.resolution).toContain('const x = 1') + expect(result.resolution).not.toContain('<<<<<<<') + }) + + it('auto-resolves with theirs strategy', () => { + const result = resolveConflicts(CONFLICT_TEXT, 'theirs') + expect(result.success).toBe(true) + expect(result.resolution).toContain('const x = 2') + }) + + it('detects no conflicts', () => { + const result = resolveConflicts('no conflict here') + expect(result.success).toBe(false) + expect(result.explanation).toContain('No git conflict markers') + }) + + it('handles multiple conflicts', () => { + const multi = `${CONFLICT_TEXT}\n\n${CONFLICT_TEXT.replace('x', 'y')}` + const result = resolveConflicts(multi, 'ours') + expect(result.success).toBe(true) + expect(result.conflictCount).toBe(2) + }) +}) diff --git a/tests/regex-tester.test.ts b/tests/regex-tester.test.ts new file mode 100644 index 0000000..fb32ea6 --- /dev/null +++ b/tests/regex-tester.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest' +import { testRegex, explainRegex } from '../src/utils/regex-tester.js' + +describe('regex-tester', () => { + it('finds simple matches', () => { + const result = testRegex({ pattern: '\\d+', flags: 'g', text: 'abc 123 def 456' }) + expect(result.valid).toBe(true) + expect(result.matches.length).toBe(2) + expect(result.matches[0].full).toBe('123') + expect(result.matches[1].full).toBe('456') + }) + + it('captures groups', () => { + const result = testRegex({ pattern: '(\\w+)=(\\d+)', flags: 'g', text: 'a=1,b=2' }) + expect(result.valid).toBe(true) + expect(result.matches[0].groups).toEqual(['a', '1']) + }) + + it('reports invalid regex', () => { + const result = testRegex({ pattern: '[', flags: '', text: 'test' }) + expect(result.valid).toBe(false) + expect(result.error).toBeDefined() + }) + + it('shows replace preview', () => { + const result = testRegex({ + pattern: '\\d+', + flags: 'g', + text: 'abc 123', + replacement: 'XXX', + }) + expect(result.replacePreview).toBe('abc XXX') + }) + + it('explains patterns', () => { + const exp = explainRegex('\\d+') + expect(exp).toContain('\\d') + }) +}) diff --git a/tests/text-transformer.test.ts b/tests/text-transformer.test.ts new file mode 100644 index 0000000..68a321c --- /dev/null +++ b/tests/text-transformer.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest' +import { transformText, TRANSFORM_OPTIONS } from '../src/utils/text-transformer.js' + +describe('text-transformer', () => { + it('converts to uppercase', () => { + const r = transformText('hello world', 'uppercase') + expect(r.success).toBe(true) + expect(r.result).toBe('HELLO WORLD') + }) + + it('converts to camelCase', () => { + const r = transformText('hello world', 'camelcase') + expect(r.success).toBe(true) + expect(r.result).toBe('helloWorld') + }) + + it('base64 encodes', () => { + const r = transformText('hello', 'base64encode') + expect(r.success).toBe(true) + expect(r.result).toBe('aGVsbG8=') + }) + + it('base64 decodes', () => { + const r = transformText('aGVsbG8=', 'base64decode') + expect(r.success).toBe(true) + expect(r.result).toBe('hello') + }) + + it('formats JSON', () => { + const r = transformText('{"a":1}', 'jsonpretty') + expect(r.success).toBe(true) + expect(r.result).toContain('"a": 1') + }) + + it('minifies JSON', () => { + const r = transformText('{\n "a": 1\n}', 'jsoncompact') + expect(r.success).toBe(true) + expect(r.result).toBe('{"a":1}') + }) + + it('sorts lines', () => { + const r = transformText('c\nb\na', 'sortlines') + expect(r.success).toBe(true) + expect(r.result).toBe('a\nb\nc') + }) + + it('has transform options', () => { + expect(TRANSFORM_OPTIONS.length).toBeGreaterThan(10) + }) + + it('handles invalid JSON', () => { + const r = transformText('not json', 'jsonpretty') + expect(r.success).toBe(false) + expect(r.error).toBeDefined() + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..76e1859 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": ["chrome", "node", "vitest/globals"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1ec5962 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import { crx } from '@crxjs/vite-plugin' +import manifest from './manifest.json' with { type: 'json' } + +export default defineConfig({ + build: { + emptyOutDir: true, + rollupOptions: { + input: { + popup: 'src/popup/popup.html', + options: 'src/options/options.html', + }, + }, + }, + plugins: [crx({ manifest })], +})