diff --git a/extensions/game-scout/.eslintrc.json b/extensions/game-scout/.eslintrc.json new file mode 100644 index 00000000000..5bfc8b77671 --- /dev/null +++ b/extensions/game-scout/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "root": true, + "extends": [ + "@raycast" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } +} \ No newline at end of file diff --git a/extensions/game-scout/.gitignore b/extensions/game-scout/.gitignore new file mode 100644 index 00000000000..6a1e1786f3a --- /dev/null +++ b/extensions/game-scout/.gitignore @@ -0,0 +1,14 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# Raycast specific files + +raycast-env.d.ts +.raycast-swift-build +.swiftpm +compiled_raycast_swift + +# misc +.DS_Store \ No newline at end of file diff --git a/extensions/game-scout/CHANGELOG.md b/extensions/game-scout/CHANGELOG.md new file mode 100644 index 00000000000..1d2662a8ea3 --- /dev/null +++ b/extensions/game-scout/CHANGELOG.md @@ -0,0 +1,5 @@ +# Game Scout Changelog + +## [Initial Release] - {PR_MERGE_DATE} + +- Initial release \ No newline at end of file diff --git a/extensions/game-scout/README.md b/extensions/game-scout/README.md new file mode 100644 index 00000000000..a02956b183a --- /dev/null +++ b/extensions/game-scout/README.md @@ -0,0 +1,80 @@ +# Game Scout + +The ultimate gaming companion for Raycast. Search across multiple storefronts, track historical lows, discover free giveaways, and catch the best daily deals without leaving your launcher. + +## Features + +* **Smart Recommendation Engine:** Heuristic scoring evaluates current prices against All-Time Lows (ATL), medians, and active bundles to generate actionable verdicts (๐Ÿ”ฅ GREAT DEAL, ๐Ÿ‘ GOOD DEAL, ๐ŸŸก FAIR PRICE, ๐Ÿ”ด HIGH PRICE). +* **Price History Charts:** Visual price trend graphs (3-month, 6-month, 1-year ranges) generated directly in the detail view. *(Note: Can be toggled off in preferences to save API limits).* +* **Bundle Content Viewer:** Drill down into active bundles to inspect pricing tiers, expiration dates, and included games. +* **Search Games:** Quickly look up any game, see its current price, all-time low, and active bundles (via **IsThereAnyDeal API**). +* **Saved Games:** Add games to your personal watchlist. Features advanced filtering (Only Deals, Biggest Discount, Best Opportunities) and a dynamic ๐Ÿ”ฅ Price Drops section. +* **Top Deals:** Discover the highest-rated game deals across 30+ official stores, powered by the CheapShark Deal Rating algorithm (via **CheapShark API** - No API key required). +* **Free Games:** Never miss a 100% free game or DLC giveaway across PC, PlayStation, Xbox, VR, and Mobile platforms (via **GamerPower API** - No API key required). +* **Manage Stores:** Globally filter results across the extension so you only see prices from the storefronts you actually use. + +## Setup + +The **Top Deals**, **Free Games**, and **Manage Stores** commands work out of the box. + +To use the **Search** and **Saved Games** features, a free API key from IsThereAnyDeal is required: + +1. Create an account at [IsThereAnyDeal](https://isthereanydeal.com/). +2. Go to the [Apps page](https://isthereanydeal.com/apps/) and click **Register App**. +3. Enter a name for the application and click **Submit**. +4. On your app's dashboard, locate the **API Keys** section on the right side. +5. Copy the generated API key. *(Important: Use the API Key, **not** the OAuth Client ID or Client Secret on the left).* +6. In Raycast, open the extension preferences and fill in: + * **IsThereAnyDeal API Key** โ€” the API key you generated. + * **Country** โ€” select your preferred region for pricing data. + * Configure optional preferences (e.g., max results, showing mature/DLC content, min discount, update frequency, and toggling the Price History Chart). + +## Commands + +| Command | Description | +| :--- | :--- | +| **Search Games** | Look up current prices, historical lows, and bundles for any game. | +| **Saved Games** | Manage your personal watchlist and track active price drops. | +| **Top Deals** | Browse the best daily discounts across 30+ official stores. | +| **Free Games** | Find 100% free games, DLCs, and giveaways across all platforms. | +| **Manage Stores** | Select which stores to include in your searches and deals. | + +## Actions + +### Global Actions +* **Enter** โ€” View detailed information (prices, charts, bundles, instructions) in full-screen. + +### Search & Saved Games +* **Cmd+S / Ctrl+S** โ€” Save / Remove game from your watchlist. +* **Cmd+B / Ctrl+B** โ€” View bundle contents (if active bundles exist). +* **Cmd+R / Ctrl+R** โ€” Force refresh price and chart data for the current game. + +### Game Detail View (Search & Saved Games) +* **Cmd+C / Ctrl+C** โ€” Copy best deal link. +* **Cmd+Shift+C / Ctrl+Shift+C** โ€” Copy game name. + +### Saved Games Specific +* **Cmd+Shift+Backspace / Ctrl+Shift+Backspace** โ€” Clear all saved games. + +### Top Deals +* **Cmd+M / Ctrl+M** โ€” View Metacritic reviews (if available). +* **Cmd+Shift+C / Ctrl+Shift+C** โ€” Copy deal link. +* **Cmd+Alt+Shift+C / Ctrl+Alt+Shift+C** โ€” Copy Metacritic link (if available). + +### Free Games +* **Cmd+I / Ctrl+I** โ€” Ignore / Restore giveaway (hides from the main list). + +### Manage Stores +* **Cmd+Shift+A / Ctrl+Shift+A** โ€” Select all stores. +* **Cmd+Shift+D / Ctrl+Shift+D** โ€” Deselect all stores. + +## Troubleshooting + +* **Getting "No Results" or "Invalid API Key" toast?** Double-check that you copied the **API Key** (from the right column) and not the OAuth Client ID from your IsThereAnyDeal app dashboard. +* **Hitting API Rate Limits?** If you check hundreds of games daily, try turning off the `Show Price History Chart` setting in the extension preferences to save 1 API call per game lookup. + +## Support + +If this extension helps you find great deals, consider buying me a coffee! + +Buy Me A Coffee \ No newline at end of file diff --git a/extensions/game-scout/assets/extension-icon.png b/extensions/game-scout/assets/extension-icon.png new file mode 100644 index 00000000000..5ba238a1c6d Binary files /dev/null and b/extensions/game-scout/assets/extension-icon.png differ diff --git a/extensions/game-scout/metadata/1.png b/extensions/game-scout/metadata/1.png new file mode 100644 index 00000000000..9b6933669b8 Binary files /dev/null and b/extensions/game-scout/metadata/1.png differ diff --git a/extensions/game-scout/metadata/2.png b/extensions/game-scout/metadata/2.png new file mode 100644 index 00000000000..5f0e8a28a67 Binary files /dev/null and b/extensions/game-scout/metadata/2.png differ diff --git a/extensions/game-scout/metadata/3.png b/extensions/game-scout/metadata/3.png new file mode 100644 index 00000000000..13203b3af9c Binary files /dev/null and b/extensions/game-scout/metadata/3.png differ diff --git a/extensions/game-scout/metadata/4.png b/extensions/game-scout/metadata/4.png new file mode 100644 index 00000000000..e602b1b5792 Binary files /dev/null and b/extensions/game-scout/metadata/4.png differ diff --git a/extensions/game-scout/metadata/5.png b/extensions/game-scout/metadata/5.png new file mode 100644 index 00000000000..e7592102df7 Binary files /dev/null and b/extensions/game-scout/metadata/5.png differ diff --git a/extensions/game-scout/metadata/6.png b/extensions/game-scout/metadata/6.png new file mode 100644 index 00000000000..b046cc78171 Binary files /dev/null and b/extensions/game-scout/metadata/6.png differ diff --git a/extensions/game-scout/metadata/7.png b/extensions/game-scout/metadata/7.png new file mode 100644 index 00000000000..69b1d36205e Binary files /dev/null and b/extensions/game-scout/metadata/7.png differ diff --git a/extensions/game-scout/package-lock.json b/extensions/game-scout/package-lock.json new file mode 100644 index 00000000000..7d5eb4619e6 --- /dev/null +++ b/extensions/game-scout/package-lock.json @@ -0,0 +1,3473 @@ +{ + "name": "game-scout", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "game-scout", + "license": "MIT", + "dependencies": { + "@raycast/api": "^1.104.12", + "@raycast/utils": "^2.2.3" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.8", + "@types/node": "^25.6.0", + "@types/react": "^19.2.14", + "eslint": "^8.57.0", + "prettier": "^3.8.3", + "typescript": "^6.0.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oclif/core": { + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.5.tgz", + "integrity": "sha512-qcdCF7NrdWPfme6Kr34wwljRCXbCVpL1WVxiNy0Ep6vbWKjxAjFQwuhqkoyL0yjI+KdwtLcOCGn5z2yzdijc8w==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.45.tgz", + "integrity": "sha512-ENrUg8rbVCjh40uvi3MC9kGbiUoEf11nyqE59RBzegeeLpRXNo/Zp27L9j1tUmPEqGgfS2/wvHPihNzkpK1FDw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4", + "ansis": "^3.16.0", + "debug": "^4.4.1", + "ejs": "^3.1.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-help": { + "version": "6.2.44", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.44.tgz", + "integrity": "sha512-x03Se2LtlOOlGfTuuubt5C4Z8NHeR4zKXtVnfycuLU+2VOMu2WpsGy9nbs3nYuInuvsIY1BizjVaTjUz060Sig==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-not-found": { + "version": "3.2.80", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.80.tgz", + "integrity": "sha512-yTLjWvR1r/Rd/cO2LxHdMCDoL5sQhBYRUcOMCmxZtWVWhx4rAZ8KVUPDVsb+SvjJDV5ADTDBgt1H52fFx7YWqg==", + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.10.1", + "@oclif/core": "^4.10.5", + "ansis": "^3.17.0", + "fast-levenshtein": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@raycast/api": { + "version": "1.104.12", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.104.12.tgz", + "integrity": "sha512-DdrtoksnzLw4q60BgFr/H+PIvIObOfJrW15duTFH7QXVx/0Vxzw9fY7wo+H2gQ2JDDAh9sEMCpc5akP3UxKjTw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4.8.4", + "@oclif/plugin-autocomplete": "^3.2.40", + "@oclif/plugin-help": "^6.2.37", + "@oclif/plugin-not-found": "^3.2.74", + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "esbuild": "^0.27.3", + "react": "19.0.0" + }, + "bin": { + "ray": "bin/run.js" + }, + "engines": { + "node": ">=22.14.0" + }, + "peerDependencies": { + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "react-devtools": "6.1.1" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react-devtools": { + "optional": true + } + } + }, + "node_modules/@raycast/api/node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@raycast/api/node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@raycast/api/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/@raycast/eslint-config": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@raycast/eslint-config/-/eslint-config-1.0.11.tgz", + "integrity": "sha512-I0Lt8bwahVGkANUBxripIxKptMBz1Ou+UXGwfqgFvKwo1gVLrnlEngxaspQJA8L5pvzQkQMwizVCSgNC3bddWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@raycast/eslint-plugin": "^1.0.11", + "@rushstack/eslint-patch": "^1.10.4", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "eslint-config-prettier": "^9.1.0" + }, + "peerDependencies": { + "eslint": ">=7", + "prettier": ">=2", + "typescript": ">=4" + } + }, + "node_modules/@raycast/eslint-config/node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-config/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-config/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@raycast/eslint-config/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-plugin": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@raycast/eslint-plugin/-/eslint-plugin-1.0.16.tgz", + "integrity": "sha512-OyFL/W75/4hlgdUUI80Eoes0HjpVrJ8I1kB/PBH2RLjbcK22TC6IwZPXvhBZ5jF962O1TqtOuHrTjySwDaa/cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.62.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@raycast/eslint-plugin/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@raycast/utils": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.2.3.tgz", + "integrity": "sha512-YwqleVl0Wk/FOq+672gtvswuwMqjNqIr+c63FhouTEHc1LJ3zaohLSkW4+UcfBjPU8HySKQm+kJqZW1iOM9fnA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "@raycast/api": ">=1.99.4", + "react": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/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/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/extensions/game-scout/package.json b/extensions/game-scout/package.json new file mode 100644 index 00000000000..113c3759f33 --- /dev/null +++ b/extensions/game-scout/package.json @@ -0,0 +1,355 @@ +{ + "name": "game-scout", + "title": "Game Scout", + "description": "The ultimate gaming companion to track prices, deals, and free giveaways.", + "icon": "extension-icon.png", + "author": "glct26", + "categories": [ + "Fun", + "Web" + ], + "license": "MIT", + "commands": [ + { + "name": "search-games", + "title": "Search Games", + "description": "Search for game prices and deals", + "mode": "view" + }, + { + "name": "saved-games", + "title": "Saved Games", + "description": "Track your saved games and their prices.", + "mode": "view" + }, + { + "name": "top-deals", + "title": "Top Deals", + "description": "View current best deals", + "mode": "view" + }, + { + "name": "free-games", + "title": "Free Games", + "description": "Discover currently free games and giveaways.", + "mode": "view" + }, +{ + "name": "manage-stores", + "title": "Manage Stores", + "description": "Select which stores to include in your searches and deals.", + "mode": "view" + } + ], + "preferences": [ + { + "name": "itadApiKey", + "type": "textfield", + "required": false, + "title": "ITAD API Key", + "description": "Your IsThereAnyDeal API Key" + }, + { + "name": "country", + "type": "dropdown", + "required": false, + "title": "Country", + "description": "Select your store region", + "default": "US", + "data": [ + { + "title": "United States", + "value": "US" + }, + { + "title": "United Kingdom", + "value": "GB" + }, + { + "title": "France", + "value": "FR" + }, + { + "title": "Australia", + "value": "AU" + }, + { + "title": "Canada", + "value": "CA" + }, + { + "title": "Brazil", + "value": "BR" + }, + { + "title": "China", + "value": "CN" + }, + { + "title": "Turkey", + "value": "TR" + }, + { + "title": "India", + "value": "IN" + }, + { + "title": "South Korea", + "value": "KR" + }, + { + "title": "Japan", + "value": "JP" + }, + { + "title": "Indonesia", + "value": "ID" + }, + { + "title": "Taiwan", + "value": "TW" + }, + { + "title": "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€", + "value": "" + }, + { + "title": "Argentina", + "value": "AR" + }, + { + "title": "Chile", + "value": "CL" + }, + { + "title": "Colombia", + "value": "CO" + }, + { + "title": "Czech Republic", + "value": "CZ" + }, + { + "title": "Denmark", + "value": "DK" + }, + { + "title": "Germany", + "value": "DE" + }, + { + "title": "Hong Kong", + "value": "HK" + }, + { + "title": "Hungary", + "value": "HU" + }, + { + "title": "Israel", + "value": "IL" + }, + { + "title": "Kazakhstan", + "value": "KZ" + }, + { + "title": "Malaysia", + "value": "MY" + }, + { + "title": "Mexico", + "value": "MX" + }, + { + "title": "New Zealand", + "value": "NZ" + }, + { + "title": "Norway", + "value": "NO" + }, + { + "title": "Peru", + "value": "PE" + }, + { + "title": "Philippines", + "value": "PH" + }, + { + "title": "Poland", + "value": "PL" + }, + { + "title": "Qatar", + "value": "QA" + }, + { + "title": "Russia", + "value": "RU" + }, + { + "title": "Saudi Arabia", + "value": "SA" + }, + { + "title": "Singapore", + "value": "SG" + }, + { + "title": "South Africa", + "value": "ZA" + }, + { + "title": "Sweden", + "value": "SE" + }, + { + "title": "Switzerland", + "value": "CH" + }, + { + "title": "Thailand", + "value": "TH" + }, + { + "title": "Ukraine", + "value": "UA" + }, + { + "title": "United Arab Emirates", + "value": "AE" + }, + { + "title": "Uruguay", + "value": "UY" + } + ] + }, + { + "name": "showMature", + "type": "checkbox", + "required": false, + "title": "Content Filters", + "label": "[Game Search] Show mature (18+) games", + "description": "Include games with mature content in search results.", + "default": false + }, + { + "name": "showDLCGameSearch", + "type": "checkbox", + "required": false, + "title": "Content Filters", + "label": "[Game Search] Show DLCs", + "description": "Include downloadable content in game search results.", + "default": true + }, + { + "name": "maxResults", + "type": "dropdown", + "required": false, + "title": "[Game Search] Max Search Results", + "description": "Number of games to fetch per search.", + "default": "25", + "data": [ + { + "title": "10 Results", + "value": "10" + }, + { + "title": "25 Results", + "value": "25" + }, + { + "title": "50 Results", + "value": "50" + } + ] + }, + { + "name": "refreshFrequency", + "type": "dropdown", + "required": false, + "title": "[Saved Games] Update Frequency", + "description": "How often to check for price updates (helps to save API limits).", + "default": "12", + "data": [ + { + "title": "Every 6 Hours", + "value": "6" + }, + { + "title": "Every 12 Hours", + "value": "12" + }, + { + "title": "Every 24 Hours", + "value": "24" + } + ] + }, + { + "name": "minDiscount", + "type": "textfield", + "required": false, + "title": "[Top Deals] Min Discount %", + "description": "Show deals with at least this savings percentage (e.g., 50).", + "placeholder": "0" + }, + { + "name": "maxPrice", + "type": "textfield", + "required": false, + "title": "[Top Deals] Max Price ($)", + "description": "Show deals below this price in USD.", + "placeholder": "e.g., 50" + }, + { + "name": "showDLCFreeGames", + "type": "checkbox", + "required": false, + "title": "", + "label": "[Free Games] Show DLCs", + "description": "Include downloadable content in the free games list.", + "default": true + }, + { + "name": "showPriceHistoryChart", + "type": "checkbox", + "required": false, + "title": "API & Performance", + "label": "[Game Details] Show Price History Chart", + "description": "Warning: Uses 1 extra API call per game to generate a 1-year price chart. Turn off if you check hundreds of games daily.", + "default": true + } + ], + "dependencies": { + "@raycast/api": "^1.104.12", + "@raycast/utils": "^2.2.3" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.8", + "@types/node": "^25.6.0", + "@types/react": "^19.2.14", + "eslint": "^8.57.0", + "prettier": "^3.8.3", + "typescript": "^6.0.3" + }, + "screenshots": [ + { "path": "metadata/1.png", "title": "Search Games" }, + { "path": "metadata/2.png", "title": "Saved Games" }, + { "path": "metadata/3.png", "title": "Game Detail - SKIP & Bundle" }, + { "path": "metadata/4.png", "title": "Game Detail - BUY & Chart" }, + { "path": "metadata/5.png", "title": "Free Games" }, + { "path": "metadata/6.png", "title": "Top Deals" }, + { "path": "metadata/7.png", "title": "Manage Stores" } + ], + "scripts": { + "dev": "ray dev", + "build": "ray build -e dist", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "publish": "npx @raycast/api@latest publish" + } +} diff --git a/extensions/game-scout/src/free-games.tsx b/extensions/game-scout/src/free-games.tsx new file mode 100644 index 00000000000..33283d5ccb2 --- /dev/null +++ b/extensions/game-scout/src/free-games.tsx @@ -0,0 +1,401 @@ +import { useEffect, useState } from "react"; +import { + List, + ActionPanel, + Action, + Icon, + Color, + Detail, + LocalStorage, + getPreferenceValues, +} from "@raycast/api"; +import { useFetch } from "@raycast/utils"; + +interface Giveaway { + id: number; + title: string; + worth: string; + thumbnail: string; + image: string; + description: string; + instructions: string; + open_giveaway: string; + published_date: string; + type: string; + platforms: string; + end_date: string; + users: number; + status: string; +} + +function parsePlatformsAndStores(platformsString: string) { + const parts = platformsString.split(", ").map((p) => p.trim()); + + const categories = { + stores: [] as string[], + pc: [] as string[], + playstation: [] as string[], + xbox: [] as string[], + switch: [] as string[], + mobile: [] as string[], + vr: [] as string[], + }; + + parts.forEach((p) => { + const lowerP = p.toLowerCase(); + + if ( + lowerP.includes("steam") || + lowerP.includes("epic") || + lowerP.includes("gog") + ) { + categories.stores.push(p); + } else if (lowerP === "pc" || lowerP === "windows") { + categories.pc.push(p); + } else if ( + lowerP.includes("playstation") || + lowerP.includes("ps3") || + lowerP.includes("ps4") || + lowerP.includes("ps5") || + lowerP.includes("vita") + ) { + categories.playstation.push(p); + } else if (lowerP.includes("xbox")) { + categories.xbox.push(p); + } else if (lowerP.includes("switch") || lowerP.includes("nintendo")) { + categories.switch.push(p); + } else if ( + lowerP.includes("android") || + lowerP.includes("ios") || + lowerP.includes("mobile") + ) { + categories.mobile.push(p); + } else if (lowerP.includes("vr")) { + categories.vr.push(p); + } else { + categories.stores.push(p); + } + }); + + const accessories: any[] = []; + + categories.stores.forEach((store) => { + let tagColor = Color.SecondaryText; + const lower = store.toLowerCase(); + if (lower.includes("steam")) tagColor = Color.Blue; + else if (lower.includes("epic")) tagColor = Color.PrimaryText; + else if (lower.includes("gog")) tagColor = Color.Purple; + accessories.push({ + tag: { value: store, color: tagColor }, + tooltip: "Storefront", + }); + }); + + if (categories.pc.length > 0) { + accessories.push({ + icon: { source: Icon.Monitor, tintColor: Color.SecondaryText }, + tooltip: categories.pc.join(", "), + }); + } + if (categories.playstation.length > 0) { + accessories.push({ + icon: { source: Icon.GameController, tintColor: Color.Blue }, + tooltip: categories.playstation.join(", "), + }); + } + if (categories.xbox.length > 0) { + accessories.push({ + icon: { source: Icon.GameController, tintColor: Color.Green }, + tooltip: categories.xbox.join(", "), + }); + } + if (categories.switch.length > 0) { + accessories.push({ + icon: { source: Icon.GameController, tintColor: Color.Red }, + tooltip: categories.switch.join(", "), + }); + } + if (categories.mobile.length > 0) { + accessories.push({ + icon: { source: Icon.Mobile, tintColor: Color.Yellow }, + tooltip: categories.mobile.join(", "), + }); + } + if (categories.vr.length > 0) { + accessories.push({ + icon: { source: Icon.Eye, tintColor: Color.Orange }, + tooltip: categories.vr.join(", "), + }); + } + + return accessories; +} + +export default function FreeGames() { + const preferences = getPreferenceValues(); + const { isLoading, data } = useFetch( + "https://www.gamerpower.com/api/giveaways", + ); + + const [ignoredIds, setIgnoredIds] = useState([]); + const [viewFilter, setViewFilter] = useState("all"); + + useEffect(() => { + LocalStorage.getItem("ignored_giveaways").then((storedIds) => { + if (storedIds) setIgnoredIds(JSON.parse(storedIds)); + }); + }, []); + + const toggleIgnore = async (id: number) => { + let newIds; + if (ignoredIds.includes(id)) { + newIds = ignoredIds.filter((i) => i !== id); + } else { + newIds = [...ignoredIds, id]; + } + setIgnoredIds(newIds); + await LocalStorage.setItem("ignored_giveaways", JSON.stringify(newIds)); + }; + + const filteredData = data?.filter((game) => { + const isIgnored = ignoredIds.includes(game.id); + const platforms = game.platforms.toLowerCase(); + const type = game.type.toLowerCase(); + + if (!preferences.showDLCFreeGames && type.includes("dlc")) { + return false; + } + + if (viewFilter === "ignored") return isIgnored; + if (isIgnored) return false; + + if (viewFilter === "all") return true; + if (viewFilter === "pc") + return platforms.includes("pc") || platforms.includes("windows"); + if (viewFilter === "ps") + return ( + platforms.includes("playstation") || + platforms.includes("ps3") || + platforms.includes("ps4") || + platforms.includes("ps5") + ); + if (viewFilter === "xbox") return platforms.includes("xbox"); + if (viewFilter === "switch") + return platforms.includes("switch") || platforms.includes("nintendo"); + if (viewFilter === "mobile") + return platforms.includes("android") || platforms.includes("ios"); + if (viewFilter === "vr") return platforms.includes("vr"); + + return true; + }); + + return ( + setViewFilter(newValue)} + > + + + + + + + + + + + + + + + } + > + {filteredData?.length === 0 && !isLoading && ( + + )} + + {filteredData?.map((game) => { + const platformAccessories = parsePlatformsAndStores(game.platforms); + const isIgnored = ignoredIds.includes(game.id); + const searchKeywords = game.platforms.split(", "); + + return ( + + + toggleIgnore(game.id)} + isIgnored={isIgnored} + /> + } + icon={Icon.Sidebar} + /> + + + + toggleIgnore(game.id)} + icon={isIgnored ? Icon.Eye : Icon.EyeDisabled} + shortcut={{ + Windows: { modifiers: ["ctrl"], key: "i" }, + macOS: { modifiers: ["cmd"], key: "i" }, + }} + /> + + + } + /> + ); + })} + + ); +} + +function GiveawayDetail({ + game, + toggleIgnore, + isIgnored, +}: { + game: Giveaway; + toggleIgnore: () => void; + isIgnored: boolean; +}) { + const endDate = + game.end_date !== "N/A" + ? new Date(game.end_date).toLocaleDateString("en-GB") + : "No end date"; + + const markdown = ` +![](${game.image}) + +# ${game.title} + +${game.description} + +--- + +### How to claim: +${game.instructions} + `; + + return ( + + + + + {game.platforms.split(", ").map((p, index) => { + const isStore = + p.toLowerCase().includes("steam") || + p.toLowerCase().includes("epic") || + p.toLowerCase().includes("gog"); + return ( + + ); + })} + + + + + + + + + + } + actions={ + + + + + } + /> + ); +} diff --git a/extensions/game-scout/src/manage-stores.tsx b/extensions/game-scout/src/manage-stores.tsx new file mode 100644 index 00000000000..15c5e6ee950 --- /dev/null +++ b/extensions/game-scout/src/manage-stores.tsx @@ -0,0 +1,139 @@ +import { useState, useEffect } from "react"; +import { + List, + ActionPanel, + Action, + Icon, + LocalStorage, + showToast, + Toast, + Color, +} from "@raycast/api"; + +// Unified store list for both ITAD and CheapShark +export const UNIFIED_STORES = [ + { id: "2game", name: "2game" }, + { id: "allyouplay", name: "AllYouPlay" }, + { id: "blizzard", name: "Blizzard Shop" }, + { id: "dlgamer", name: "DLGamer" }, + { id: "ea", name: "EA Store (Origin)" }, + { id: "epic", name: "Epic Games Store" }, + { id: "etailmarket", name: "eTail.Market" }, + { id: "fanatical", name: "Fanatical" }, + { id: "gamebillet", name: "GameBillet" }, + { id: "gamersgate", name: "GamersGate" }, + { id: "gamesplanet", name: "GamesPlanet" }, + { id: "gog", name: "GOG" }, + { id: "gmg", name: "Green Man Gaming" }, + { id: "humble", name: "Humble Store" }, + { id: "indiegala", name: "IndieGala" }, + { id: "joybuggy", name: "JoyBuggy" }, + { id: "microsoft", name: "Microsoft Store" }, + { id: "planetplay", name: "PlanetPlay" }, + { id: "steam", name: "Steam" }, + { id: "ubisoft", name: "Ubisoft Store" }, + { id: "voidu", name: "Voidu" }, + { id: "wingamestore", name: "WinGameStore / MacGameStore" }, + { id: "other", name: "Other (Unlisted Stores)" }, +]; + +export default function ManageStores() { + const [selectedIds, setSelectedIds] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const init = async () => { + const stored = await LocalStorage.getItem("selected_stores"); + if (stored) { + setSelectedIds(JSON.parse(stored)); + } else { + setSelectedIds(UNIFIED_STORES.map((s) => s.id)); + } + setIsLoading(false); + }; + init(); + }, []); + + const toggleStore = async (id: string) => { + const isCurrentlySelected = selectedIds.includes(id); + + if (isCurrentlySelected && selectedIds.length === 1) { + await showToast({ + style: Toast.Style.Failure, + title: "Cannot Deselect", + message: "At least one store must remain selected.", + }); + return; + } + + const newIds = isCurrentlySelected + ? selectedIds.filter((sId) => sId !== id) + : [...selectedIds, id]; + + setSelectedIds(newIds); + await LocalStorage.setItem("selected_stores", JSON.stringify(newIds)); + }; + + const selectAll = async () => { + const allIds = UNIFIED_STORES.map((s) => s.id); + setSelectedIds(allIds); + await LocalStorage.setItem("selected_stores", JSON.stringify(allIds)); + }; + + const selectNone = () => { + setSelectedIds([]); + // Don't save to the LocalStorage + }; + + return ( + + {UNIFIED_STORES.map((store) => { + const isSelected = selectedIds.includes(store.id); + + return ( + + + toggleStore(store.id)} + icon={isSelected ? Icon.Circle : Icon.CheckCircle} + /> + + + + + + + } + /> + ); + })} + + ); +} diff --git a/extensions/game-scout/src/saved-games.tsx b/extensions/game-scout/src/saved-games.tsx new file mode 100644 index 00000000000..3f6e8659182 --- /dev/null +++ b/extensions/game-scout/src/saved-games.tsx @@ -0,0 +1,1400 @@ +// TODO: Add 24h timestamp to referencePrices for time-based price change tags + +import { useEffect, useState, useMemo } from "react"; +import { + List, + ActionPanel, + Action, + Icon, + Color, + Detail, + LocalStorage, + getPreferenceValues, + Cache, + openExtensionPreferences, + Image, +} from "@raycast/api"; + +const preferences = getPreferenceValues<{ + itadApiKey: string; + country: string; + refreshFrequency: string; + showPriceHistoryChart: boolean; +}>(); + +const API_KEY = (preferences.itadApiKey || "").trim(); +const COUNTRY = preferences.country; + +const cache = new Cache(); +const CACHE_KEY = `itad_saved_prices_v1_${COUNTRY}`; +const CACHE_TTL = + parseInt(preferences.refreshFrequency || "12") * 60 * 60 * 1000; +const DETAIL_CACHE_TTL = 6 * 60 * 60 * 1000; +const RECENT_BUNDLE_WINDOW = 2 * 365 * 24 * 60 * 60 * 1000; + +import { formatPrice, isStoreAllowed } from "./utils"; + +const cleanBundleUrl = (url?: string) => { + if (!url) return ""; + if (url.includes("u=http")) { + try { + return decodeURIComponent(url.split("u=")[1].split("&")[0]); + } catch { + return url; + } + } + return url; +}; + +export default function SavedGames() { + const isApiKeyValid = API_KEY.length > 0; + if (!isApiKeyValid) + return ( + + + + + } + /> + + ); + + const [savedGames, setSavedGames] = useState< + { id: string; title: string; slug: string; type?: string }[] + >([]); + const [rawPrices, setRawPrices] = useState>({}); + const [bundleCounts, setBundleCounts] = useState>({}); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [refreshKey, setRefreshKey] = useState(0); + const [referencePrices, setReferencePrices] = useState< + Record + >({}); + const [isLoading, setIsLoading] = useState(true); + const [selectedStores, setSelectedStores] = useState(["all"]); + const [filterMode, setFilterMode] = useState("default"); + + useEffect(() => { + LocalStorage.getItem("selected_stores").then((s) => + setSelectedStores(s ? JSON.parse(s) : ["all"]), + ); + LocalStorage.getItem("saved_itad_games").then((s) => + s ? setSavedGames(JSON.parse(s)) : setIsLoading(false), + ); + LocalStorage.getItem("last_seen_prices").then( + (s) => s && setReferencePrices(JSON.parse(s)), + ); + }, [refreshKey]); + + const fetchPrices = async () => { + if (savedGames.length === 0) { + setIsLoading(false); + return; + } + setIsLoading(true); + try { + const cachedData = cache.get(CACHE_KEY); + if (cachedData) { + const parsed = JSON.parse(cachedData); + if (Date.now() - parsed.timestamp < CACHE_TTL) { + setRawPrices(parsed.rawPrices); + setBundleCounts(parsed.bundleCounts); + setIsLoading(false); + return; + } + } + + const gameIds = savedGames.map((g) => g.id); + const [pRes, oRes] = await Promise.all([ + fetch( + `https://api.isthereanydeal.com/games/prices/v2?key=${API_KEY}&country=${COUNTRY}&nondeals=true`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(gameIds), + }, + ), + fetch( + `https://api.isthereanydeal.com/games/overview/v2?key=${API_KEY}&country=${COUNTRY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(gameIds), + }, + ), + ]); + const [pJson, oJson] = await Promise.all([pRes.json(), oRes.json()]); + + const rMap: any = {}; + const newLastSeen: Record = {}; + + (Array.isArray(pJson) ? pJson : Object.values(pJson)).forEach( + (it: any) => { + rMap[it.id] = it.deals || []; + const bestDeal = it.deals?.find((d: any) => + isStoreAllowed(d.shop?.name || "", selectedStores), + ); + if (bestDeal?.price?.amount != null) { + newLastSeen[it.id] = bestDeal.price.amount; + } + }, + ); + + const now = Date.now(); + const bMap: any = {}; + gameIds.forEach((id) => { + bMap[id] = (oJson.bundles || []).filter((b: any) => { + const isNotExpired = !b.expiry || new Date(b.expiry).getTime() > now; + return ( + isNotExpired && + b.tiers?.some((t: any) => t.games?.some((gm: any) => gm.id === id)) + ); + }).length; + }); + + setRawPrices(rMap); + setBundleCounts(bMap); + + if (Object.keys(rMap).length > 0) { + cache.set( + CACHE_KEY, + JSON.stringify({ + timestamp: Date.now(), + rawPrices: rMap, + bundleCounts: bMap, + }), + ); + } + + const existing = await LocalStorage.getItem("last_seen_prices"); + const parsed = existing ? JSON.parse(existing) : {}; + const merged = { ...parsed, ...newLastSeen }; + + await LocalStorage.setItem("last_seen_prices", JSON.stringify(merged)); + } catch (e: any) { + if (e.name !== "AbortError" && !e.message?.includes("aborted")) + console.error(e); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchPrices(); + }, [savedGames]); + + const prices = useMemo(() => { + const map: any = {}; + Object.keys(rawPrices).forEach((id) => { + if (!rawPrices[id] || !Array.isArray(rawPrices[id])) { + map[id] = null; + return; + } + map[id] = + rawPrices[id].filter((d: any) => + isStoreAllowed(d.shop?.name || "", selectedStores), + )[0] || null; + }); + return map; + }, [rawPrices, selectedStores]); + + const scoreGame = (id: string) => { + const deal = prices[id]; + if (!deal) return -999; + const price = deal.price?.amount ?? 999; + const cut = deal.cut ?? 0; + const bundle = bundleCounts[id] ?? 0; + const normDiscount = cut / 100; + const normPrice = 1 - Math.min(price / 60, 1); + const bundleBonus = bundle > 0 ? 0.15 : 0; + return normDiscount * 0.5 + normPrice * 0.35 + bundleBonus; + }; + + const sortedAndFilteredGames = useMemo(() => { + let list = [...savedGames]; + if (filterMode === "deals") { + list = list.filter((g) => prices[g.id] && prices[g.id].cut > 0); + } else if (filterMode === "discount") { + list.sort((a, b) => (prices[b.id]?.cut || 0) - (prices[a.id]?.cut || 0)); + } else if (filterMode === "opportunity") { + list.sort((a, b) => scoreGame(b.id) - scoreGame(a.id)); + } else if (filterMode === "lowest") { + list.sort((a, b) => { + const pA = prices[a.id]?.price?.amount ?? 999999; + const pB = prices[b.id]?.price?.amount ?? 999999; + return pA - pB; + }); + } + return list; + }, [savedGames, prices, filterMode]); + + const removeGame = async (id: string) => { + const newList = savedGames.filter((g) => g.id !== id); + setSavedGames(newList); + await LocalStorage.setItem("saved_itad_games", JSON.stringify(newList)); + cache.remove(CACHE_KEY); + }; + + const majorDrops = savedGames.filter((game) => { + const last = referencePrices[game.id]; + const current = prices[game.id]?.price?.amount; + if (!last || !current) return false; + const diff = ((current - last) / last) * 100; + return diff <= -10; + }); + + return ( + + + + + + + + } + > + {savedGames.length === 0 && !isLoading ? ( + + ) : ( + <> + {majorDrops.length > 0 && filterMode === "default" && ( + + )} + {majorDrops.length > 0 && filterMode === "default" && ( + + {sortedAndFilteredGames + .filter((g) => majorDrops.some((d) => d.id === g.id)) + .map((game) => { + const deal = prices[game.id]; + const diff = + ((deal?.price?.amount - referencePrices[game.id]) / + referencePrices[game.id]) * + 100; + return ( + + removeGame(game.id)} + /> + } + icon={Icon.Sidebar} + /> + + } + /> + ); + })} + + )} + {sortedAndFilteredGames + .filter( + (g) => + filterMode !== "default" || + !majorDrops.some((d) => d.id === g.id), + ) + .map((game) => { + const deal = prices[game.id]; + const acc = []; + + if (!deal && isLoading) { + acc.push({ + icon: Icon.Clock, + tooltip: "Loading price...", + tintColor: Color.SecondaryText, + }); + } else if (deal) { + const currentPrice = deal.price?.amount; + const lastPrice = referencePrices[game.id]; + + if (lastPrice && currentPrice !== lastPrice) { + const diffAbs = currentPrice - lastPrice; + const diffPct = (diffAbs / lastPrice) * 100; + + if (Math.abs(diffPct) >= 3) { + let label = ""; + if (diffPct <= -10) label = "๐Ÿ”ฅ DROP"; + else if (diffPct < 0) label = "โฌ‡ DOWN"; + else if (diffPct >= 10) label = "โš ๏ธ SPIKE"; + else label = "โฌ† UP"; + + acc.push({ + tag: { + value: `${label} ${diffPct > 0 ? "+" : ""}${diffPct.toFixed(0)}%`, + color: diffPct > 0 ? Color.Red : Color.Green, + }, + }); + } + } + + const regularPrice = deal.regular?.amount; + const currency = deal.price?.currency; + const cut = deal.cut || 0; + + if ( + cut > 0 && + regularPrice != null && + regularPrice > currentPrice + ) { + acc.push({ + text: `${formatPrice(regularPrice, currency)} โ†’ ${formatPrice(currentPrice, currency)}`, + }); + } else { + acc.push({ text: formatPrice(currentPrice, currency) }); + } + + if (cut > 0) { + acc.push({ tag: { value: `-${cut}%`, color: Color.Green } }); + } + } + + if (bundleCounts[game.id] > 0) { + acc.push({ + icon: { source: Icon.Box, tintColor: Color.Purple }, + tooltip: "Available in a Bundle", + }); + } + const isMusic = + (!game.type || game.type === "dlc" || game.type === "OTHER") && + (game.title?.toLowerCase().endsWith(" ost") || + game.title?.toLowerCase().includes("soundtrack")); + const cleanType = isMusic + ? "SOUNDTRACK" + : game.type === "game" || game.type === "base" + ? undefined + : game.type?.toUpperCase() || undefined; + + let listIcon: any = Icon.Star; + if (deal?.cut >= 80) { + listIcon = { source: Icon.Star, tintColor: Color.Red }; + } else if (deal?.cut >= 50) { + listIcon = { source: Icon.Star, tintColor: Color.Orange }; + } else if (deal?.cut > 0) { + listIcon = { source: Icon.Star, tintColor: Color.Green }; + } + + return ( + + + removeGame(game.id)} + /> + } + icon={Icon.Sidebar} + /> + {deal?.url && ( + + )} + + + removeGame(game.id)} + icon={Icon.Trash} + style={Action.Style.Destructive} + shortcut={{ + Windows: { modifiers: ["ctrl"], key: "s" }, + macOS: { modifiers: ["cmd"], key: "s" }, + }} + /> + { + setSavedGames([]); + await LocalStorage.setItem( + "saved_itad_games", + JSON.stringify([]), + ); + cache.remove(CACHE_KEY); + }} + icon={Icon.Trash} + style={Action.Style.Destructive} + shortcut={{ + Windows: { + modifiers: ["ctrl", "shift"], + key: "backspace", + }, + macOS: { + modifiers: ["cmd", "shift"], + key: "backspace", + }, + }} + /> + + + } + /> + ); + })} + + )} + + ); +} + +function GameDetail({ + gameId, + gameTitle, + gameSlug, + gameType, + removeGame, +}: any) { + const [data, setData] = useState({ + steamData: null, + realBundles: [], + deals: [], + historyLow: null, + overview: null, + historyChart: [], + lastChecked: null, + }); + const [isLoading, setIsLoading] = useState(true); + const [refreshKey, setRefreshKey] = useState(0); + const [range, setRange] = useState<"3m" | "6m" | "1y">("1y"); + const SHOW_CHART = preferences.showPriceHistoryChart ?? true; + const [selectedStores, setSelectedStores] = useState(["all"]); + + useEffect(() => { + LocalStorage.getItem("selected_stores").then((s) => + setSelectedStores(s ? JSON.parse(s) : ["all"]), + ); + LocalStorage.getItem("preferred_chart_range").then( + (s) => s && setRange(s as any), + ); + }, [refreshKey]); + + const handleSetRange = (r: "3m" | "6m" | "1y") => { + setRange(r); + LocalStorage.setItem("preferred_chart_range", r); + }; + + useEffect(() => { + let isMounted = true; + const abort = new AbortController(); + const detailCacheKey = `search_detail_${gameId}_${COUNTRY}_v1`; + const fetchDetailData = async () => { + setIsLoading(true); + const cached = cache.get(detailCacheKey); + if (cached) { + const parsed = JSON.parse(cached); + if (Date.now() - parsed.timestamp < DETAIL_CACHE_TTL) { + if (isMounted) { + setData({ ...parsed.data, lastChecked: parsed.timestamp }); + setIsLoading(false); + } + return; + } + } + try { + const searchRes = await fetch( + `https://store.steampowered.com/api/storesearch/?term=${encodeURIComponent(gameTitle)}&l=english&cc=US`, + { signal: abort.signal }, + ); + const searchJson = await searchRes.json(); + const targetItem = + searchJson?.items?.find( + (i: any) => i.name.toLowerCase() === gameTitle.toLowerCase(), + ) || searchJson?.items?.[0]; + let steamData = null; + if (targetItem?.id) { + const detailRes = await fetch( + `https://store.steampowered.com/api/appdetails?appids=${targetItem.id}&l=english`, + { signal: abort.signal }, + ); + steamData = (await detailRes.json())?.[targetItem.id]?.data || null; + } + const fetchPromises = [ + fetch( + `https://api.isthereanydeal.com/games/bundles/v2?key=${API_KEY}&id=${gameId}`, + { signal: abort.signal }, + ), + fetch( + `https://api.isthereanydeal.com/games/prices/v2?key=${API_KEY}&country=${COUNTRY}&nondeals=true`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + fetch( + `https://api.isthereanydeal.com/games/historylow/v1?key=${API_KEY}&country=${COUNTRY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + fetch( + `https://api.isthereanydeal.com/games/overview/v2?key=${API_KEY}&country=${COUNTRY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + fetch( + `https://api.isthereanydeal.com/games/history/v2?key=${API_KEY}&id=${gameId}&country=${COUNTRY}`, + { signal: abort.signal }, + ), + ]; + const jsons = await Promise.all( + (await Promise.all(fetchPromises)).map((r) => r.json()), + ); + const combined = { + steamData, + realBundles: Array.isArray(jsons[0]) + ? jsons[0] + : jsons[0]?.[gameId]?.bundles || [], + deals: + (Array.isArray(jsons[1]) + ? jsons[1][0]?.deals + : jsons[1]?.[gameId]?.deals) || [], + historyLow: + (Array.isArray(jsons[2]) + ? jsons[2][0]?.low + : jsons[2]?.[gameId]?.low) || null, + overview: Array.isArray(jsons[3]) ? jsons[3][0] : jsons[3], + historyChart: Array.isArray(jsons[4]) ? jsons[4] : [], + }; + if (isMounted) { + cache.set( + detailCacheKey, + JSON.stringify({ timestamp: Date.now(), data: combined }), + ); + setData({ ...combined, lastChecked: Date.now() }); + } + } catch (e: any) { + if (e.name !== "AbortError" && !e.message?.includes("aborted")) + console.error(e); + } finally { + if (isMounted) setIsLoading(false); + } + }; + fetchDetailData(); + return () => { + isMounted = false; + abort.abort(); + }; + }, [gameId, gameTitle, SHOW_CHART, refreshKey]); + + const { + steamData, + realBundles, + deals = [], + historyLow, + overview, + historyChart, + lastChecked, + } = data; + + // โฑ๏ธ TIME DOMAIN: Single deterministic snapshot + const now = useMemo(() => Date.now(), [refreshKey]); + + // ๐Ÿ“ฆ BUNDLE DOMAIN: Single source of truth + const bundle = useMemo(() => { + const isBundleActive = (b: any) => { + if (!b?.expiry) return true; + const t = new Date(b.expiry).getTime(); + return Number.isFinite(t) && t > now; + }; + + const activeBundles = realBundles.filter(isBundleActive); + const activeCount = activeBundles.length; + + const recentBundles = realBundles.filter((b: any) => { + const tsRaw = b.created ?? b.timestamp; + const ts = tsRaw ? new Date(tsRaw).getTime() : null; + return ts && ts < now && now - ts < RECENT_BUNDLE_WINDOW; + }); + + const totalBundles = + realBundles?.length > 0 + ? realBundles.length + : typeof overview?.bundles === "number" + ? overview.bundles + : overview?.bundles?.count || overview?.bundles?.length || 0; + + const allBundlesForTs = Array.isArray(overview?.bundles) + ? overview.bundles + : realBundles; + + const timestamps = allBundlesForTs + .map((b: any) => { + const ts = b.created ?? b.timestamp ?? b.publish ?? b.expiry; + return ts ? new Date(ts).getTime() : null; + }) + .filter((t: any) => t !== null && !isNaN(t)); + + const lastBundleTs = + timestamps.length > 0 ? Math.max(...timestamps) : undefined; + const lastBundleDate = lastBundleTs ? new Date(lastBundleTs) : null; + const lastBundleAgo = lastBundleTs ? now - lastBundleTs : Infinity; + + const ONE_YEAR = 365 * 24 * 60 * 60 * 1000; + + let state: string | null = null; + let icon: Image.ImageLike | undefined = undefined; + let color: Color | undefined = undefined; + + if (activeCount > 0) { + state = "Active"; + icon = Icon.Box; + color = Color.Purple; + } else if (totalBundles === 0) { + state = "Never Bundled"; + icon = Icon.XMarkCircle; + color = Color.SecondaryText; + } else if (recentBundles.length >= 4 && lastBundleAgo < ONE_YEAR) { + state = "Frequent"; + icon = Icon.Repeat; + color = Color.Orange; + } else if (totalBundles >= 2 && lastBundleAgo < ONE_YEAR) { + state = "Occasional"; + icon = Icon.Circle; + color = Color.SecondaryText; + } else if (totalBundles === 1 && lastBundleAgo < ONE_YEAR) { + state = "Bundled recently"; + icon = Icon.Circle; + color = Color.SecondaryText; + } else if (lastBundleDate) { + const month = lastBundleDate.toLocaleString("en-US", { month: "short" }); + const year = lastBundleDate.getFullYear(); + state = `Last bundled ${month} ${year}`; + icon = Icon.Clock; + color = Color.SecondaryText; + } else if (totalBundles > 0) { + state = `Bundled ${totalBundles} times`; + icon = Icon.Clock; + color = Color.SecondaryText; + } + + const getLowestPrice = (bundle: any) => { + const prices = bundle.tiers + ?.map((t: any) => t.price?.amount) + .filter((p: number | undefined) => typeof p === "number"); + return prices?.length ? Math.min(...prices) : Infinity; + }; + + const featuredBundle = + activeBundles.length > 0 + ? activeBundles.reduce( + (best: any, current: any) => + getLowestPrice(current) < getLowestPrice(best) ? current : best, + activeBundles[0], + ) + : null; + + const featuredPrice = featuredBundle + ? getLowestPrice(featuredBundle) + : null; + + const getGameTierPrice = (b: any) => { + const tiersWithGame = b.tiers?.filter((t: any) => + t.games?.some((gm: any) => gm.id === gameId), + ); + if (tiersWithGame && tiersWithGame.length > 0) { + const prices = tiersWithGame + .map((t: any) => t.price?.amount) + .filter((p: any) => typeof p === "number"); + return prices.length > 0 ? Math.min(...prices) : Infinity; + } + return Infinity; + }; + + const bestGameTierPrice = + activeBundles.length > 0 + ? Math.min(...activeBundles.map(getGameTierPrice)) + : null; + + const actualBundlePrice = + bestGameTierPrice !== Infinity && bestGameTierPrice !== null + ? bestGameTierPrice + : featuredPrice; + + return { + activeBundles, + activeCount, + recentBundles, + totalBundles, + state, + icon, + color, + featuredBundle, + featuredPrice, + actualBundlePrice, + getLowestPrice, + }; + }, [realBundles, overview, now, gameId]); + + const allowedHistory = useMemo(() => { + return (historyChart || []).filter( + (pt: any) => + pt.deal?.price?.amount != null && + isStoreAllowed(pt.shop?.name || "", selectedStores), + ); + }, [historyChart, selectedStores]); + + const filteredDeals = deals.filter((d: any) => + isStoreAllowed(d.shop?.name || "", selectedStores), + ); + const currentBest = filteredDeals?.[0]; + const currentPrice = currentBest?.price?.amount; + + const bundleValue = useMemo(() => { + if (!bundle.activeBundles.length || currentPrice == null) return null; + + for (const b of bundle.activeBundles) { + let gameTierIndex = -1; + b.tiers?.forEach((t: any, i: number) => { + if ( + t.games?.some((gm: any) => gm.id === gameId || gm.name === gameTitle) + ) { + gameTierIndex = i; + } + }); + + if (gameTierIndex === -1) continue; + + const tierPrice = b.tiers[gameTierIndex]?.price?.amount; + if (!tierPrice) continue; + + if (tierPrice < currentPrice) { + return { + type: "better", + message: "Cheaper in active bundle", + tier: b.tiers[gameTierIndex], + bundle: b, + }; + } + + let totalGames = 0; + for (let i = 0; i <= gameTierIndex; i++) { + totalGames += b.tiers[i]?.games?.length || 0; + } + + const unitPrice = totalGames > 0 ? tierPrice / totalGames : tierPrice; + if (unitPrice < currentPrice) { + return { + type: "value", + message: "Better value in bundle", + tier: b.tiers[gameTierIndex], + bundle: b, + }; + } + } + return null; + }, [bundle.activeBundles, currentPrice, gameId, gameTitle]); + + const allTimeLow = historyLow?.price?.amount ?? historyLow?.amount; + const hCurrency = + historyLow?.price?.currency ?? historyLow?.currency ?? "USD"; + + // ๐Ÿงฎ SCORE DOMAIN: Untouched heuristic engine + const twelveMonthTime = now - 365 * 24 * 60 * 60 * 1000; + const statsPrices = allowedHistory + .filter((pt: any) => new Date(pt.timestamp).getTime() >= twelveMonthTime) + .map((pt: any) => pt.deal.price.amount); + + let typicalMin: number | null = null; + let typicalMax: number | null = null; + let median: number | null = null; + + if (statsPrices.length > 0) { + const sorted = [...statsPrices].sort((a, b) => a - b); + const filtered = sorted.slice( + Math.floor(sorted.length * 0.1), + Math.floor(sorted.length * 0.9) + 1, + ); + if (filtered.length > 0) { + typicalMin = Math.min(...filtered); + typicalMax = Math.max(...filtered); + const mid = Math.floor(filtered.length / 2); + median = + filtered.length % 2 !== 0 + ? filtered[mid] + : (filtered[mid - 1] + filtered[mid]) / 2; + } + } + + const cut = currentBest?.cut || 0; + let verdict = ""; + let reason: string | undefined; + let recommendation = ""; + + const mapUI = (v: string) => { + switch (v) { + case "Free": + return { badge: "free", color: Color.Blue, icon: Icon.Gift }; + case "Strong deal": + return { badge: "best", color: Color.Green, icon: Icon.Star }; + case "Good deal": + return { badge: "good", color: Color.Green, icon: Icon.ThumbsUp }; + case "Fair price": + return { + badge: "neutral", + color: Color.SecondaryText, + icon: Icon.Minus, + }; + case "Not great": + case "Not ideal": + return { badge: "weak", color: Color.Orange, icon: Icon.Clock }; + case "Overpriced": + return { badge: "bad", color: Color.Red, icon: Icon.XMarkCircle }; + default: + return { + badge: "neutral", + color: Color.SecondaryText, + icon: Icon.Minus, + }; + } + }; + + if (currentPrice === 0 || cut === 100) { + recommendation = "๐Ÿ†“ FREE"; + verdict = "Free"; + reason = "Free to claim"; + } else if (bundle.activeCount > 0 && bundleValue?.type === "better") { + recommendation = "๐Ÿ”ด HIGH PRICE"; + verdict = "Overpriced"; + reason = bundleValue.message; + } else if (bundle.activeCount > 0 && bundleValue?.type === "value") { + recommendation = "๐ŸŸก FAIR PRICE"; + verdict = "Not ideal"; + reason = bundleValue.message; + } else if (currentPrice != null) { + let score = 0; + const safeATL = + allTimeLow && allTimeLow > 0 ? allTimeLow : currentPrice || 1; + const ratioATL = currentPrice / safeATL; + + if (ratioATL <= 0.95) score += 0.35; + else if (ratioATL <= 1.05) score += 0.25; + else if (ratioATL <= 1.2) score += 0.1; + else if (ratioATL >= 2) score -= 0.2; + + if (median != null && median > 0) { + const ratioMedian = currentPrice / median; + if (ratioMedian <= 0.75) score += 0.25; + else if (ratioMedian <= 0.9) score += 0.15; + else if (ratioMedian >= 1.25) score -= 0.2; + } + + if (cut >= 75 && ratioATL <= 1.2) score += 0.4; + else if (cut >= 75) score += 0.3; + else if (cut >= 50) score += 0.2; + else if (cut >= 25) score += 0.1; + else if (cut > 0) score += 0.05; + + score = Math.max(0, Math.min(1, score)); + + // Recommendation thresholds + if (score >= 0.7) recommendation = "๐Ÿ”ฅ GREAT DEAL"; + else if (score >= 0.5) recommendation = "๐Ÿ‘ GOOD DEAL"; + else if (score >= 0.35) recommendation = "๐ŸŸก FAIR PRICE"; + else recommendation = "๐Ÿ”ด HIGH PRICE"; + + const isATL = currentPrice <= safeATL; + const isNearATL = currentPrice <= safeATL * 1.05; + const isBelowAvg = median && currentPrice < median * 0.85; + const isAtTypical = median && currentPrice <= median * 1.05; + + if (score < 0.35) { + if (cut === 0 && (!median || isAtTypical)) { + verdict = "Fair price"; + reason = "Typical price for this game"; + recommendation = "๐ŸŸก FAIR PRICE"; + } else { + verdict = cut > 0 ? "Not ideal" : "Overpriced"; + reason = + cut > 0 ? "Discounted, but still high" : "Above usual price range"; + } + } else if (score < 0.5) { + verdict = "Not ideal"; + if (bundle.state === "Frequent") reason = "Frequently bundled, wait"; + else if (cut >= 70) reason = "Big discount, not lowest"; + else + reason = cut > 0 ? "Small discount, better wait" : "No discount, wait"; + } else { + if (isATL) { + verdict = score >= 0.7 ? "Strong deal" : "Good deal"; + reason = cut > 0 ? "At all-time low price" : "Lowest recorded price"; + } else if (isNearATL) { + verdict = "Good deal"; + reason = "Near all-time low price"; + } else if (cut >= 75) { + verdict = "Good deal"; + reason = "Large discount applied"; + } else if (isBelowAvg) { + verdict = "Good deal"; + reason = "Well below usual price"; + } else { + verdict = "Fair price"; + reason = "Decent price, not the lowest"; + } + } + } + + if (!reason) { + reason = "Typical pricing"; + } + + const plotData: any[] = []; + const cutoffTime = + now - + (range === "3m" ? 90 : range === "6m" ? 180 : 365) * 24 * 60 * 60 * 1000; + if (allowedHistory.length > 0) { + allowedHistory + .filter((pt: any) => new Date(pt.timestamp).getTime() >= cutoffTime) + .reverse() + .forEach((pt: any) => { + plotData.push({ + x: new Date(pt.timestamp).toISOString().split("T")[0], + y: pt.deal.price.amount, + }); + }); + } + + let chartUrl = ""; + if (SHOW_CHART && plotData.length > 0) { + const minY = Math.min(...plotData.map((p) => p.y)); + const datasets: any[] = [ + { + data: plotData, + borderColor: "#2ecc71", + backgroundColor: "rgba(46, 204, 113, 0.05)", + steppedLine: true, + fill: true, + pointRadius: plotData.map((p) => (Math.abs(p.y - minY) < 0.01 ? 4 : 0)), + pointBackgroundColor: plotData.map((p) => + Math.abs(p.y - minY) < 0.01 ? "#e74c3c" : "transparent", + ), + pointBorderColor: plotData.map((p) => + Math.abs(p.y - minY) < 0.01 ? "#ffffff" : "transparent", + ), + pointBorderWidth: 2, + borderWidth: 2, + }, + ]; + + if (median !== null) { + datasets.push({ + data: plotData.map((p) => ({ x: p.x, y: median })), + borderColor: "rgba(255, 255, 255, 0.2)", + borderWidth: 1, + borderDash: [5, 5], + fill: false, + pointRadius: 0, + }); + } + + const config: any = { + type: "line", + data: { datasets }, + options: { + layout: { padding: { right: 30, left: 5, top: 10, bottom: 5 } }, + legend: { display: false }, + scales: { + xAxes: [ + { + type: "time", + time: { + parser: "YYYY-MM-DD", + unit: "month", + displayFormats: { month: "MMM YY" }, + }, + gridLines: { color: "rgba(255, 255, 255, 0.1)" }, + ticks: { maxRotation: 0, maxTicksLimit: 6, fontSize: 8 }, + }, + ], + yAxes: [ + { + gridLines: { color: "rgba(255, 255, 255, 0.1)" }, + ticks: { beginAtZero: true, fontSize: 8 }, + }, + ], + }, + }, + }; + chartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(config))}&w=250&h=110&devicePixelRatio=2&bkg=transparent`; + } + + const isDiscounted = currentBest && currentBest.cut > 0; + let saleTagText = ""; + if (isDiscounted) { + if (currentBest.cut >= 70) saleTagText = "MEGA SALE"; + else if (currentBest.cut >= 40) saleTagText = "ON SALE"; + else saleTagText = "DISCOUNT"; + } + + const heroSection = + currentBest && currentPrice != null + ? `

${recommendation || "Price Details"}

\n

${formatPrice(currentPrice, currentBest.price?.currency)} ${isDiscounted ? `-${currentBest.cut}%` : ""} ยท ${currentBest.shop?.name}

\n\n---\n\n` + : ""; + + const markdown = ` +${steamData?.header_image ? `\n\n` : ""} +# ${gameTitle} +${ + steamData?.genres + ? `*${steamData.genres + .map((g: any) => g.description) + .slice(0, 2) + .join( + ", ", + )}*${steamData?.release_date?.date ? ` ยท ${new Date(steamData.release_date.date).getFullYear()}` : ""}` + : "" +} + +${steamData?.short_description ? `> ${steamData.short_description.replace(/<[^>]*>?/gm, "").split(". ")[0]}.` : ""} + +${heroSection} +๐Ÿ’ฐ **Prices in ${COUNTRY}** + +| Store | Price | RRP | Discount | +| :--- | :--- | :--- | :--- | +${filteredDeals?.length ? filteredDeals.map((p: any) => `| ${p.url ? `[${p.shop?.name}](${p.url})` : p.shop?.name} | **${formatPrice(p.price?.amount, p.price?.currency)}** | ${formatPrice(p.regular?.amount, p.price?.currency)} | ${p.cut > 0 ? "-" + p.cut + "%" : "-"} |`).join("\n") : "| No data found | - | - | - |"} + +${chartUrl ? `\n---\n\n๐Ÿ“ˆ **Trend: ${range === "1y" ? "12 Months" : range === "6m" ? "6 Months" : "3 Months"}**\n\n![Price History](${chartUrl})\n` : ""} +`; + + return ( + + {recommendation && ( + + )} + {verdict && + ["๐Ÿ”ฅ GREAT DEAL", "๐Ÿ‘ GOOD DEAL", "๐Ÿ†“ FREE"].includes( + recommendation, + ) && ( + + )} + {reason && ( + 28 ? reason.slice(0, 25) + "..." : reason} + /> + )} + {(isDiscounted || bundle.activeCount > 0) && ( + <> + + + {isDiscounted && ( + + )} + {bundle.activeCount > 0 && ( + + )} + + + )} + + + {typicalMin !== null && + typicalMax !== null && + typicalMin !== typicalMax && ( + + )} + {median !== null && ( + + )} + + {bundle.state && ( + <> + + + + )} + {bundleValue?.tier && bundleValue?.bundle && ( + + )} + + + = 23 + ? "All Stores" + : `${selectedStores.length} Selected` + } + /> + + + + + {gameSlug && ( + + )} + {steamData?.steam_appid && ( + + )} + {lastChecked && ( + <> + + + + )} + + } + actions={ + + + {currentBest?.url && ( + + )} + + {currentBest?.url && ( + + )} + + handleSetRange("3m")} + icon={range === "3m" ? Icon.Checkmark : Icon.Circle} + /> + handleSetRange("6m")} + icon={range === "6m" ? Icon.Checkmark : Icon.Circle} + /> + handleSetRange("1y")} + icon={range === "1y" ? Icon.Checkmark : Icon.Circle} + /> + + { + const c = new Cache(); + c.remove(`search_detail_${gameId}_${COUNTRY}_v1`); + setIsLoading(true); + setRefreshKey((k) => k + 1); + }} + /> + {realBundles.length > 0 && ( + + } + icon={Icon.Box} + shortcut={{ + Windows: { modifiers: ["ctrl"], key: "b" }, + macOS: { modifiers: ["cmd"], key: "b" }, + }} + /> + )} + {removeGame && ( + + )} + + + } + /> + ); +} + +function BundleContentViewer({ bundles, gameTitle }: any) { + const firstBundleUrl = cleanBundleUrl( + bundles?.[0]?.url || bundles?.[0]?.details, + ); + + let markdown = `# ๐Ÿ“ฆ Bundle Contents for ${gameTitle}\n\n`; + bundles.forEach((b: any, i: number) => { + const active = b.expiry ? new Date(b.expiry) > new Date() : true; + markdown += `## ${active ? "โœ…" : "โŒ"} ${b.title || `Bundle ${i + 1}`}\n**Page:** ${b.page?.name || "Unknown"}${b.expiry ? ` | **Expires:** ${new Date(b.expiry).toLocaleDateString("en-GB")}` : ""}\n${b.note ? `\n> ${b.note}` : ""}\n\n`; + b.tiers?.forEach((t: any, ti: number) => { + markdown += `### ${t.name || `Tier ${ti + 1}`} - **${t.price ? formatPrice(t.price.amount, t.price.currency) : "N/A"}**\n`; + t.games?.forEach( + (g: any) => (markdown += `- ${g.title || g.name || g}\n`), + ); + markdown += `\n`; + }); + }); + return ( + + + + + ) : undefined + } + /> + ); +} diff --git a/extensions/game-scout/src/search-games.tsx b/extensions/game-scout/src/search-games.tsx new file mode 100644 index 00000000000..7f3aef118b1 --- /dev/null +++ b/extensions/game-scout/src/search-games.tsx @@ -0,0 +1,1353 @@ +import { useEffect, useState, useMemo } from "react"; +import { useFetch } from "@raycast/utils"; +import { + List, + ActionPanel, + Action, + Icon, + Color, + Detail, + LocalStorage, + Cache, + getPreferenceValues, + openExtensionPreferences, + Image, +} from "@raycast/api"; + +const preferences = getPreferenceValues<{ + itadApiKey: string; + country: string; + maxResults: string; + showMature: boolean; + showDLCGameSearch: boolean; + showPriceHistoryChart: boolean; +}>(); + +const API_KEY = (preferences.itadApiKey || "").trim(); +const COUNTRY = preferences.country; +const MAX_RESULTS = parseInt(preferences.maxResults) || 25; + +const detailCache = new Cache({ namespace: "search_detail" }); +const DETAIL_CACHE_TTL = 6 * 60 * 60 * 1000; +const RECENT_BUNDLE_WINDOW = 2 * 365 * 24 * 60 * 60 * 1000; + +import { formatPrice, isStoreAllowed } from "./utils"; + +const cleanBundleUrl = (url?: string) => { + if (!url) return ""; + if (url.includes("u=http")) { + try { + return decodeURIComponent(url.split("u=")[1].split("&")[0]); + } catch { + return url; + } + } + return url; +}; + +export default function Command() { + const [apiError, setApiError] = useState(false); + const isApiKeyValid = API_KEY.length > 0; + const isCountryValid = COUNTRY.length === 2; + + if (!isApiKeyValid || !isCountryValid) { + return ( + + + + + } + /> + + ); + } + + const [searchText, setSearchText] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [searchData, setSearchData] = useState([]); + const [loadingSearch, setLoadingSearch] = useState(false); + const [savedGames, setSavedGames] = useState< + { id: string; title: string; slug: string; type?: string }[] + >([]); + + useEffect(() => { + LocalStorage.getItem("saved_itad_games").then( + (stored) => stored && setSavedGames(JSON.parse(stored)), + ); + }, []); + + const toggleSave = async (game: any) => { + let newList; + if (savedGames.some((g) => g.id === game.id)) { + newList = savedGames.filter((g) => g.id !== game.id); + } else { + newList = [ + ...savedGames, + { + id: game.id, + title: game.title, + slug: game.slug, + type: game.type || "OTHER", + }, + ]; + + const savedCache = new Cache(); + savedCache.remove(`itad_saved_prices_v1_${COUNTRY}`); + } + setSavedGames(newList); + await LocalStorage.setItem("saved_itad_games", JSON.stringify(newList)); + }; + + useEffect(() => { + if (!searchQuery) { + setSearchData([]); + return; + } + const fetchData = async () => { + setLoadingSearch(true); + try { + const res = await fetch( + `https://api.isthereanydeal.com/games/search/v1?key=${API_KEY}&title=${encodeURIComponent(searchQuery)}`, + ); + if (res.status === 401 || res.status === 403) { + setApiError(true); + setLoadingSearch(false); + return; + } + const json = await res.json(); + const results = Array.isArray(json) + ? json + : json.data || json.results || []; + const query = searchQuery.toLowerCase(); + + const score = (t: string) => { + const lower = t.toLowerCase(); + if (lower === query) return 0; + if (lower.startsWith(query)) return 1; + if (lower.includes(query)) return 2; + return 3; + }; + results.sort((a: any, b: any) => score(a.title) - score(b.title)); + + setSearchData(results); + } catch { + setSearchData([]); + } + setLoadingSearch(false); + }; + fetchData(); + }, [searchQuery]); + + const gameIds = searchData?.slice(0, MAX_RESULTS).map((g: any) => g.id) || []; + + const { data: priceData, isLoading: priceLoading } = useFetch( + `https://api.isthereanydeal.com/games/overview/v2?key=${API_KEY}&country=${COUNTRY}&nondeals=true`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(gameIds), + execute: gameIds.length > 0 && searchQuery.length > 0, + mapResult: (res: any) => ({ + data: Array.isArray(res) ? res : Object.values(res).flat(), + }), + }, + ); + + const bundleCounts = useMemo(() => { + const counts: Record = {}; + const now = Date.now(); + + if (priceData) { + const bundles = (Array.isArray(priceData) ? priceData : []).filter( + (item: any) => { + const hasTiers = Array.isArray(item.tiers); + const isNotExpired = + !item.expiry || new Date(item.expiry).getTime() > now; + return hasTiers && isNotExpired; + }, + ); + + bundles.forEach((b: any) => { + b.tiers?.forEach((t: any) => { + t.games?.forEach((gm: any) => { + if (gm.id) { + counts[gm.id] = (counts[gm.id] || 0) + 1; + } + }); + }); + }); + } + return counts; + }, [priceData]); + + const filteredData = searchData.filter((game: any) => { + if (!preferences.showMature && game.mature) return false; + if (!preferences.showDLCGameSearch && game.type === "dlc") return false; + return true; + }); + + const isTyping = + searchText.trim() !== searchQuery && searchText.trim().length > 0; + + return ( + { + setSearchText(t); + if (t.trim() === "") setSearchQuery(""); + }} + searchBarPlaceholder="Search games (e.g. Elden Ring)..." + > + {apiError ? ( + + + + } + /> + ) : searchQuery.length === 0 ? ( + + setSearchQuery(searchText.trim())} + icon={Icon.MagnifyingGlass} + /> + + } + /> + ) : filteredData.length === 0 && !loadingSearch ? ( + + setSearchQuery(searchText.trim())} + icon={Icon.MagnifyingGlass} + /> + + } + /> + ) : ( + filteredData.slice(0, MAX_RESULTS).map((game: any) => { + const overview = Array.isArray(priceData) + ? priceData.find((p: any) => p.id === game.id) + : undefined; + const deal = overview?.current || overview; + const isSaved = savedGames.some((g) => g.id === game.id); + + const accessories = []; + if (game.mature) + accessories.push({ + tag: { value: "18+", color: Color.Red }, + tooltip: "Mature Content", + }); + + if (priceLoading && !deal) { + accessories.push({ + icon: Icon.Clock, + tooltip: "Loading price...", + tintColor: Color.SecondaryText, + }); + } else if (deal) { + const currentAmount = deal.price?.amount; + const regularAmount = deal.regular?.amount; + const currency = deal.price?.currency; + const cut = deal.cut || 0; + + if ( + cut > 0 && + regularAmount != null && + regularAmount > currentAmount + ) { + accessories.push({ + text: `${formatPrice(regularAmount, currency)} โ†’ ${formatPrice(currentAmount, currency)}`, + }); + accessories.push({ + tag: { value: `-${cut}%`, color: Color.Green }, + }); + } else { + accessories.push({ text: formatPrice(currentAmount, currency) }); + } + + if ( + typeof bundleCounts !== "undefined" && + bundleCounts[game.id] > 0 + ) { + accessories.push({ + icon: { source: Icon.Box, tintColor: Color.Purple }, + tooltip: "Available in a Bundle", + }); + } + } + const isMusic = + (game.type === null || game.type === "dlc") && + (game.title?.toLowerCase().endsWith(" ost") || + game.title?.toLowerCase().includes("soundtrack")); + const cleanType = isMusic + ? "SOUNDTRACK" + : game.type === "game" || game.type === "base" + ? undefined + : game.type?.toUpperCase() || undefined; + + return ( + + + {isTyping ? ( + setSearchQuery(searchText.trim())} + icon={Icon.MagnifyingGlass} + /> + ) : ( + toggleSave(game)} + /> + } + icon={Icon.Sidebar} + /> + )} + toggleSave(game)} + icon={isSaved ? Icon.Trash : Icon.Star} + shortcut={{ + Windows: { modifiers: ["ctrl"], key: "s" }, + macOS: { modifiers: ["cmd"], key: "s" }, + }} + style={ + isSaved + ? Action.Style.Destructive + : Action.Style.Regular + } + /> + + + } + /> + ); + }) + )} + + ); +} + +function GameDetail({ + gameId, + gameTitle, + gameSlug, + gameType, + isSaved, + toggleSave, + removeGame, +}: any) { + const [data, setData] = useState({ + steamData: null, + realBundles: [], + deals: [], + historyLow: null, + overview: null, + historyChart: [], + lastChecked: null, + }); + const [isLoading, setIsLoading] = useState(true); + const [range, setRange] = useState<"3m" | "6m" | "1y">("1y"); + const SHOW_CHART = preferences.showPriceHistoryChart ?? true; + const [selectedStores, setSelectedStores] = useState(["all"]); + const [refreshKey, setRefreshKey] = useState(0); + + useEffect(() => { + LocalStorage.getItem("selected_stores").then((s) => + setSelectedStores(s ? JSON.parse(s) : ["all"]), + ); + LocalStorage.getItem("preferred_chart_range").then( + (s) => s && setRange(s as any), + ); + }, [refreshKey]); + + const handleSetRange = (r: "3m" | "6m" | "1y") => { + setRange(r); + LocalStorage.setItem("preferred_chart_range", r); + }; + + useEffect(() => { + let isMounted = true; + const abort = new AbortController(); + const detailCacheKey = `search_detail_${gameId}_${COUNTRY}_v1`; + + const fetchDetailData = async () => { + setIsLoading(true); + const cached = detailCache.get(detailCacheKey); + if (cached) { + const parsed = JSON.parse(cached); + if (Date.now() - parsed.timestamp < DETAIL_CACHE_TTL) { + if (isMounted) { + setData({ ...parsed.data, lastChecked: parsed.timestamp }); + setIsLoading(false); + } + return; + } + } + + try { + const searchRes = await fetch( + `https://store.steampowered.com/api/storesearch/?term=${encodeURIComponent(gameTitle)}&l=english&cc=US`, + { signal: abort.signal }, + ); + const searchJson = await searchRes.json(); + + let targetItem = searchJson?.items?.find( + (item: any) => item.name.toLowerCase() === gameTitle.toLowerCase(), + ); + if (!targetItem) { + targetItem = searchJson?.items?.find((item: any) => { + const sName = item.name.toLowerCase(); + const iName = gameTitle.toLowerCase(); + if (sName.includes(iName) || iName.includes(sName)) { + const sNums = sName.match(/\b\d+\b/g) || []; + const iNums = iName.match(/\b\d+\b/g) || []; + return sNums.every((n: string) => iNums.includes(n)); + } + return false; + }); + } + if (!targetItem) targetItem = searchJson?.items?.[0]; + + let steamData = null; + if (targetItem?.id) { + const detailRes = await fetch( + `https://store.steampowered.com/api/appdetails?appids=${targetItem.id}&l=english`, + { signal: abort.signal }, + ); + steamData = (await detailRes.json())?.[targetItem.id]?.data || null; + } + + const fetchPromises = [ + fetch( + `https://api.isthereanydeal.com/games/bundles/v2?key=${API_KEY}&id=${gameId}`, + { signal: abort.signal }, + ), + fetch( + `https://api.isthereanydeal.com/games/prices/v2?key=${API_KEY}&country=${COUNTRY}&nondeals=true`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + fetch( + `https://api.isthereanydeal.com/games/historylow/v1?key=${API_KEY}&country=${COUNTRY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + fetch( + `https://api.isthereanydeal.com/games/overview/v2?key=${API_KEY}&country=${COUNTRY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([gameId]), + signal: abort.signal, + }, + ), + ]; + if (SHOW_CHART) + fetchPromises.push( + fetch( + `https://api.isthereanydeal.com/games/history/v2?key=${API_KEY}&id=${gameId}&country=${COUNTRY}`, + { signal: abort.signal }, + ), + ); + + const jsons = await Promise.all( + (await Promise.all(fetchPromises)).map((r) => r.json()), + ); + const combined = { + steamData, + realBundles: Array.isArray(jsons[0]) + ? jsons[0] + : jsons[0]?.[gameId]?.bundles || [], + deals: + (Array.isArray(jsons[1]) + ? jsons[1][0]?.deals + : jsons[1]?.[gameId]?.deals) || [], + historyLow: + (Array.isArray(jsons[2]) + ? jsons[2][0]?.low + : jsons[2]?.[gameId]?.low) || null, + overview: Array.isArray(jsons[3]) ? jsons[3][0] : jsons[3], + historyChart: Array.isArray(jsons[4]) ? jsons[4] : [], + }; + + if (isMounted) { + detailCache.set( + detailCacheKey, + JSON.stringify({ timestamp: Date.now(), data: combined }), + ); + setData({ ...combined, lastChecked: Date.now() }); + } + } catch (e: any) { + if (e.name !== "AbortError" && !e.message?.includes("aborted")) + console.error(e); + } finally { + if (isMounted) setIsLoading(false); + } + }; + fetchDetailData(); + return () => { + isMounted = false; + abort.abort(); + }; + }, [gameId, gameTitle, SHOW_CHART, refreshKey]); + + const { + steamData, + realBundles, + deals, + historyLow, + overview, + historyChart, + lastChecked, + } = data; + + // โฑ๏ธ TIME DOMAIN: Single deterministic snapshot + const now = useMemo(() => Date.now(), [refreshKey]); + + // ๐Ÿ“ฆ BUNDLE DOMAIN: Single source of truth + const bundle = useMemo(() => { + const isBundleActive = (b: any) => { + if (!b?.expiry) return true; + const t = new Date(b.expiry).getTime(); + return Number.isFinite(t) && t > now; + }; + + const activeBundles = realBundles.filter(isBundleActive); + const activeCount = activeBundles.length; + + const recentBundles = realBundles.filter((b: any) => { + const tsRaw = b.created ?? b.timestamp; + const ts = tsRaw ? new Date(tsRaw).getTime() : null; + return ts && ts < now && now - ts < RECENT_BUNDLE_WINDOW; + }); + + const totalBundles = + realBundles?.length > 0 + ? realBundles.length + : typeof overview?.bundles === "number" + ? overview.bundles + : overview?.bundles?.count || overview?.bundles?.length || 0; + + const allBundlesForTs = Array.isArray(overview?.bundles) + ? overview.bundles + : realBundles; + + const timestamps = allBundlesForTs + .map((b: any) => { + const ts = b.created ?? b.timestamp ?? b.publish ?? b.expiry; + return ts ? new Date(ts).getTime() : null; + }) + .filter((t: any) => t !== null && !isNaN(t)); + + const lastBundleTs = + timestamps.length > 0 ? Math.max(...timestamps) : undefined; + const lastBundleDate = lastBundleTs ? new Date(lastBundleTs) : null; + const lastBundleAgo = lastBundleTs ? now - lastBundleTs : Infinity; + + const ONE_YEAR = 365 * 24 * 60 * 60 * 1000; + + let state: string | null = null; + let icon: Image.ImageLike | undefined = undefined; + let color: Color | undefined = undefined; + + if (activeCount > 0) { + state = "Active"; + icon = Icon.Box; + color = Color.Purple; + } else if (totalBundles === 0) { + state = "Never Bundled"; + icon = Icon.XMarkCircle; + color = Color.SecondaryText; + } else if (recentBundles.length >= 4 && lastBundleAgo < ONE_YEAR) { + state = "Frequent"; + icon = Icon.Repeat; + color = Color.Orange; + } else if (totalBundles >= 2 && lastBundleAgo < ONE_YEAR) { + state = "Occasional"; + icon = Icon.Circle; + color = Color.SecondaryText; + } else if (totalBundles === 1 && lastBundleAgo < ONE_YEAR) { + state = "Bundled recently"; + icon = Icon.Circle; + color = Color.SecondaryText; + } else if (lastBundleDate) { + const month = lastBundleDate.toLocaleString("en-US", { month: "short" }); + const year = lastBundleDate.getFullYear(); + state = `Last bundled ${month} ${year}`; + icon = Icon.Clock; + color = Color.SecondaryText; + } else if (totalBundles > 0) { + state = `Bundled ${totalBundles} times`; + icon = Icon.Clock; + color = Color.SecondaryText; + } + + const getLowestPrice = (bundle: any) => { + const prices = bundle.tiers + ?.map((t: any) => t.price?.amount) + .filter((p: number | undefined) => typeof p === "number"); + return prices?.length ? Math.min(...prices) : Infinity; + }; + + const featuredBundle = + activeBundles.length > 0 + ? activeBundles.reduce( + (best: any, current: any) => + getLowestPrice(current) < getLowestPrice(best) ? current : best, + activeBundles[0], + ) + : null; + + const featuredPrice = featuredBundle + ? getLowestPrice(featuredBundle) + : null; + + const getGameTierPrice = (b: any) => { + const tiersWithGame = b.tiers?.filter((t: any) => + t.games?.some((gm: any) => gm.id === gameId), + ); + if (tiersWithGame && tiersWithGame.length > 0) { + const prices = tiersWithGame + .map((t: any) => t.price?.amount) + .filter((p: any) => typeof p === "number"); + return prices.length > 0 ? Math.min(...prices) : Infinity; + } + return Infinity; + }; + + const bestGameTierPrice = + activeBundles.length > 0 + ? Math.min(...activeBundles.map(getGameTierPrice)) + : null; + + const actualBundlePrice = + bestGameTierPrice !== Infinity && bestGameTierPrice !== null + ? bestGameTierPrice + : featuredPrice; + + return { + activeBundles, + activeCount, + recentBundles, + totalBundles, + state, + icon, + color, + featuredBundle, + featuredPrice, + actualBundlePrice, + getLowestPrice, + }; + }, [realBundles, overview, now, gameId]); + + const allowedHistory = useMemo(() => { + return (historyChart || []).filter( + (pt: any) => + pt.deal?.price?.amount != null && + isStoreAllowed(pt.shop?.name || "", selectedStores), + ); + }, [historyChart, selectedStores]); + + const filteredDeals = deals.filter((d: any) => + isStoreAllowed(d.shop?.name || "", selectedStores), + ); + const currentBest = filteredDeals?.[0]; + const currentPrice = currentBest?.price?.amount; + + const bundleValue = useMemo(() => { + if (!bundle.activeBundles.length || currentPrice == null) return null; + + for (const b of bundle.activeBundles) { + let gameTierIndex = -1; + b.tiers?.forEach((t: any, i: number) => { + if ( + t.games?.some((gm: any) => gm.id === gameId || gm.name === gameTitle) + ) { + gameTierIndex = i; + } + }); + + if (gameTierIndex === -1) continue; + + const tierPrice = b.tiers[gameTierIndex]?.price?.amount; + if (!tierPrice) continue; + + if (tierPrice < currentPrice) { + return { + type: "better", + message: "Cheaper in active bundle", + tier: b.tiers[gameTierIndex], + bundle: b, + }; + } + + let totalGames = 0; + for (let i = 0; i <= gameTierIndex; i++) { + totalGames += b.tiers[i]?.games?.length || 0; + } + + const unitPrice = totalGames > 0 ? tierPrice / totalGames : tierPrice; + if (unitPrice < currentPrice) { + return { + type: "value", + message: "Better value in bundle", + tier: b.tiers[gameTierIndex], + bundle: b, + }; + } + } + return null; + }, [bundle.activeBundles, currentPrice, gameId, gameTitle]); + + const allTimeLow = historyLow?.price?.amount ?? historyLow?.amount; + const hCurrency = + historyLow?.price?.currency ?? historyLow?.currency ?? "USD"; + + // ๐Ÿงฎ SCORE DOMAIN: Untouched heuristic engine + const twelveMonthTime = now - 365 * 24 * 60 * 60 * 1000; + const statsPrices = allowedHistory + .filter((pt: any) => new Date(pt.timestamp).getTime() >= twelveMonthTime) + .map((pt: any) => pt.deal.price.amount); + + let typicalMin: number | null = null; + let typicalMax: number | null = null; + let median: number | null = null; + + if (statsPrices.length > 0) { + const sorted = [...statsPrices].sort((a, b) => a - b); + const filtered = sorted.slice( + Math.floor(sorted.length * 0.1), + Math.floor(sorted.length * 0.9) + 1, + ); + if (filtered.length > 0) { + typicalMin = Math.min(...filtered); + typicalMax = Math.max(...filtered); + const mid = Math.floor(filtered.length / 2); + median = + filtered.length % 2 !== 0 + ? filtered[mid] + : (filtered[mid - 1] + filtered[mid]) / 2; + } + } + + const cut = currentBest?.cut || 0; + let verdict = ""; + let reason: string | undefined; + let recommendation = ""; + + const mapUI = (v: string) => { + switch (v) { + case "Free": + return { badge: "free", color: Color.Blue, icon: Icon.Gift }; + case "Strong deal": + return { badge: "best", color: Color.Green, icon: Icon.Star }; + case "Good deal": + return { badge: "good", color: Color.Green, icon: Icon.ThumbsUp }; + case "Fair price": + return { + badge: "neutral", + color: Color.SecondaryText, + icon: Icon.Minus, + }; + case "Not great": + case "Not ideal": + return { badge: "weak", color: Color.Orange, icon: Icon.Clock }; + case "Overpriced": + return { badge: "bad", color: Color.Red, icon: Icon.XMarkCircle }; + default: + return { + badge: "neutral", + color: Color.SecondaryText, + icon: Icon.Minus, + }; + } + }; + + if (currentPrice === 0 || cut === 100) { + recommendation = "๐Ÿ†“ FREE"; + verdict = "Free"; + reason = "Free to claim"; + } else if (bundle.activeCount > 0 && bundleValue?.type === "better") { + recommendation = "๐Ÿ”ด HIGH PRICE"; + verdict = "Overpriced"; + reason = bundleValue.message; + } else if (bundle.activeCount > 0 && bundleValue?.type === "value") { + recommendation = "๐ŸŸก FAIR PRICE"; + verdict = "Not ideal"; + reason = bundleValue.message; + } else if (currentPrice != null) { + let score = 0; + const safeATL = + allTimeLow && allTimeLow > 0 ? allTimeLow : currentPrice || 1; + const ratioATL = currentPrice / safeATL; + + if (ratioATL <= 0.95) score += 0.35; + else if (ratioATL <= 1.05) score += 0.25; + else if (ratioATL <= 1.2) score += 0.1; + else if (ratioATL >= 2) score -= 0.2; + + if (median != null && median > 0) { + const ratioMedian = currentPrice / median; + if (ratioMedian <= 0.75) score += 0.25; + else if (ratioMedian <= 0.9) score += 0.15; + else if (ratioMedian >= 1.25) score -= 0.2; + } + + if (cut >= 75 && ratioATL <= 1.2) score += 0.4; + else if (cut >= 75) score += 0.3; + else if (cut >= 50) score += 0.2; + else if (cut >= 25) score += 0.1; + else if (cut > 0) score += 0.05; + + score = Math.max(0, Math.min(1, score)); + + // Recommendation thresholds + if (score >= 0.7) recommendation = "๐Ÿ”ฅ GREAT DEAL"; + else if (score >= 0.5) recommendation = "๐Ÿ‘ GOOD DEAL"; + else if (score >= 0.35) recommendation = "๐ŸŸก FAIR PRICE"; + else recommendation = "๐Ÿ”ด HIGH PRICE"; + + const isATL = currentPrice <= safeATL; + const isNearATL = currentPrice <= safeATL * 1.05; + const isBelowAvg = median && currentPrice < median * 0.85; + const isAtTypical = median && currentPrice <= median * 1.05; + + if (score < 0.35) { + if (cut === 0 && (!median || isAtTypical)) { + verdict = "Fair price"; + reason = "Typical price for this game"; + recommendation = "๐ŸŸก FAIR PRICE"; + } else { + verdict = cut > 0 ? "Not ideal" : "Overpriced"; + reason = + cut > 0 ? "Discounted, but still high" : "Above usual price range"; + } + } else if (score < 0.5) { + verdict = "Not ideal"; + if (bundle.state === "Frequent") reason = "Frequently bundled, wait"; + else if (cut >= 70) reason = "Big discount, not lowest"; + else + reason = cut > 0 ? "Small discount, better wait" : "No discount, wait"; + } else { + if (isATL) { + verdict = score >= 0.7 ? "Strong deal" : "Good deal"; + reason = cut > 0 ? "At all-time low price" : "Lowest recorded price"; + } else if (isNearATL) { + verdict = "Good deal"; + reason = "Near all-time low price"; + } else if (cut >= 75) { + verdict = "Good deal"; + reason = "Large discount applied"; + } else if (isBelowAvg) { + verdict = "Good deal"; + reason = "Well below usual price"; + } else { + verdict = "Fair price"; + reason = "Decent price, not the lowest"; + } + } + } + + if (!reason) { + reason = "Typical pricing"; + } + + const plotData: any[] = []; + const cutoffTime = + now - + (range === "3m" ? 90 : range === "6m" ? 180 : 365) * 24 * 60 * 60 * 1000; + if (allowedHistory.length > 0) { + allowedHistory + .filter((pt: any) => new Date(pt.timestamp).getTime() >= cutoffTime) + .reverse() + .forEach((pt: any) => { + plotData.push({ + x: new Date(pt.timestamp).toISOString().split("T")[0], + y: pt.deal.price.amount, + }); + }); + } + + let chartUrl = ""; + if (SHOW_CHART && plotData.length > 0) { + const minY = Math.min(...plotData.map((p) => p.y)); + const datasets: any[] = [ + { + data: plotData, + borderColor: "#2ecc71", + backgroundColor: "rgba(46, 204, 113, 0.05)", + steppedLine: true, + fill: true, + pointRadius: plotData.map((p) => (Math.abs(p.y - minY) < 0.01 ? 4 : 0)), + pointBackgroundColor: plotData.map((p) => + Math.abs(p.y - minY) < 0.01 ? "#e74c3c" : "transparent", + ), + pointBorderColor: plotData.map((p) => + Math.abs(p.y - minY) < 0.01 ? "#ffffff" : "transparent", + ), + pointBorderWidth: 2, + borderWidth: 2, + }, + ]; + + if (median !== null) { + datasets.push({ + data: plotData.map((p) => ({ x: p.x, y: median })), + borderColor: "rgba(255, 255, 255, 0.2)", + borderWidth: 1, + borderDash: [5, 5], + fill: false, + pointRadius: 0, + }); + } + + const config: any = { + type: "line", + data: { datasets }, + options: { + layout: { padding: { right: 30, left: 5, top: 10, bottom: 5 } }, + legend: { display: false }, + scales: { + xAxes: [ + { + type: "time", + time: { + parser: "YYYY-MM-DD", + unit: "month", + displayFormats: { month: "MMM YY" }, + }, + gridLines: { color: "rgba(255, 255, 255, 0.1)" }, + ticks: { maxRotation: 0, maxTicksLimit: 6, fontSize: 8 }, + }, + ], + yAxes: [ + { + gridLines: { color: "rgba(255, 255, 255, 0.1)" }, + ticks: { beginAtZero: true, fontSize: 8 }, + }, + ], + }, + annotation: { + annotations: [ + { + type: "line", + mode: "horizontal", + scaleID: "y-axis-0", + value: minY, + borderColor: "rgba(231, 76, 60, 0.8)", + borderWidth: 1, + borderDash: [2, 2], + label: { + enabled: true, + content: "ATL", + position: "right", + backgroundColor: "rgba(231, 76, 60, 0.8)", + fontSize: 8, + yAdjust: 6, + }, + }, + ], + }, + }, + }; + chartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(config))}&w=250&h=110&devicePixelRatio=2&bkg=transparent`; + } + + const isDiscounted = currentBest && currentBest.cut > 0; + let saleTagText = ""; + if (isDiscounted) { + if (currentBest.cut >= 70) saleTagText = "MEGA SALE"; + else if (currentBest.cut >= 40) saleTagText = "ON SALE"; + else saleTagText = "DISCOUNT"; + } + + const heroSection = + currentBest && currentPrice != null + ? `

${recommendation || "Price Details"}

\n

${formatPrice(currentPrice, currentBest.price?.currency)} ${isDiscounted ? `-${currentBest.cut}%` : ""} ยท ${currentBest.shop?.name}

\n\n---\n\n` + : ""; + + const markdown = ` +${steamData?.header_image ? `\n\n` : ""} +# ${gameTitle} +${ + steamData?.genres + ? `*${steamData.genres + .map((g: any) => g.description) + .slice(0, 2) + .join( + ", ", + )}*${steamData?.release_date?.date ? ` ยท ${new Date(steamData.release_date.date).getFullYear()}` : ""}` + : "" +} + +${steamData?.short_description ? `> ${steamData.short_description.replace(/<[^>]*>?/gm, "").split(". ")[0]}.` : ""} + +${heroSection} +๐Ÿ’ฐ **Prices in ${COUNTRY}** + +| Store | Price | RRP | Discount | +| :--- | :--- | :--- | :--- | +${filteredDeals?.length ? filteredDeals.map((p: any) => `| ${p.url ? `[${p.shop?.name}](${p.url})` : p.shop?.name} | **${formatPrice(p.price?.amount, p.price?.currency)}** | ${formatPrice(p.regular?.amount, p.price?.currency)} | ${p.cut > 0 ? "-" + p.cut + "%" : "-"} |`).join("\n") : "| No data found | - | - | - |"} + +${chartUrl ? `\n---\n\n๐Ÿ“ˆ **Trend: ${range === "1y" ? "12 Months" : range === "6m" ? "6 Months" : "3 Months"}**\n\n![Price History](${chartUrl})\n` : ""} +`; + + return ( + + {recommendation && ( + + )} + {verdict && + ["๐Ÿ”ฅ GREAT DEAL", "๐Ÿ‘ GOOD DEAL", "๐Ÿ†“ FREE"].includes( + recommendation, + ) && ( + + )} + {reason && ( + 28 ? reason.slice(0, 25) + "..." : reason} + /> + )} + {(isDiscounted || bundle.activeCount > 0) && ( + <> + + + {isDiscounted && ( + + )} + {bundle.activeCount > 0 && ( + + )} + + + )} + + + {typicalMin !== null && + typicalMax !== null && + typicalMin !== typicalMax && ( + + )} + {median !== null && ( + + )} + + {bundle.state && ( + <> + + + + )} + {bundleValue?.tier && bundleValue?.bundle && ( + + )} + + + = 23 + ? "All Stores" + : `${selectedStores.length} Selected` + } + /> + + + + + {gameSlug && ( + + )} + {steamData?.steam_appid && ( + + )} + {lastChecked && ( + <> + + + + )} + + } + actions={ + + + {currentBest?.url && ( + + )} + + {currentBest?.url && ( + + )} + + handleSetRange("3m")} + icon={range === "3m" ? Icon.Checkmark : Icon.Circle} + /> + handleSetRange("6m")} + icon={range === "6m" ? Icon.Checkmark : Icon.Circle} + /> + handleSetRange("1y")} + icon={range === "1y" ? Icon.Checkmark : Icon.Circle} + /> + + { + const c = new Cache({ namespace: "search_detail" }); + c.remove(`search_detail_${gameId}_${COUNTRY}_v1`); + setIsLoading(true); + setRefreshKey((k) => k + 1); + }} + /> + {realBundles.length > 0 && ( + + } + icon={Icon.Box} + shortcut={{ + Windows: { modifiers: ["ctrl"], key: "b" }, + macOS: { modifiers: ["cmd"], key: "b" }, + }} + /> + )} + {toggleSave && ( + + )} + {removeGame && ( + + )} + + + } + /> + ); +} + +function BundleContentViewer({ bundles, gameTitle }: any) { + const firstBundleUrl = cleanBundleUrl( + bundles?.[0]?.url || bundles?.[0]?.details, + ); + + let markdown = `# ๐Ÿ“ฆ Bundle Contents for ${gameTitle}\n\n`; + bundles.forEach((b: any, i: number) => { + const active = b.expiry ? new Date(b.expiry) > new Date() : true; + markdown += `## ${active ? "โœ…" : "โŒ"} ${b.title || `Bundle ${i + 1}`}\n**Page:** ${b.page?.name || "Unknown"}${b.expiry ? ` | **Expires:** ${new Date(b.expiry).toLocaleDateString("en-GB")}` : ""}\n${b.note ? `\n> ${b.note}` : ""}\n\n`; + b.tiers?.forEach((t: any, ti: number) => { + markdown += `### ${t.name || `Tier ${ti + 1}`} - **${t.price ? formatPrice(t.price.amount, t.price.currency) : "N/A"}**\n`; + t.games?.forEach( + (g: any) => (markdown += `- ${g.title || g.name || g}\n`), + ); + markdown += `\n`; + }); + }); + return ( + + + + + ) : undefined + } + /> + ); +} diff --git a/extensions/game-scout/src/top-deals.tsx b/extensions/game-scout/src/top-deals.tsx new file mode 100644 index 00000000000..e0f1428fae8 --- /dev/null +++ b/extensions/game-scout/src/top-deals.tsx @@ -0,0 +1,218 @@ +import { useEffect, useState } from "react"; +import { + List, + ActionPanel, + Action, + Icon, + Color, + Cache, + getPreferenceValues, + LocalStorage, +} from "@raycast/api"; + +const STORES: { [key: string]: { name: string; color: Color } } = { + "1": { name: "Steam", color: Color.Blue }, + "2": { name: "GamersGate", color: Color.SecondaryText }, + "3": { name: "Green Man Gaming", color: Color.Green }, + "4": { name: "Amazon", color: Color.Orange }, + "5": { name: "GameStop", color: Color.Red }, + "6": { name: "Direct2Drive", color: Color.SecondaryText }, + "7": { name: "GOG", color: Color.Purple }, + "8": { name: "Origin", color: Color.Orange }, + "9": { name: "Get Games", color: Color.SecondaryText }, + "10": { name: "ShinyLoot", color: Color.SecondaryText }, + "11": { name: "Humble Store", color: Color.Red }, + "12": { name: "Desura", color: Color.SecondaryText }, + "13": { name: "Uplay", color: Color.Blue }, + "14": { name: "IndieGameStand", color: Color.SecondaryText }, + "15": { name: "Fanatical", color: Color.Orange }, + "16": { name: "Gamesrocket", color: Color.SecondaryText }, + "17": { name: "Games Republic", color: Color.SecondaryText }, + "18": { name: "Sila Games", color: Color.SecondaryText }, + "19": { name: "Playfield", color: Color.SecondaryText }, + "20": { name: "ImperialGames", color: Color.SecondaryText }, + "21": { name: "WinGameStore", color: Color.SecondaryText }, + "22": { name: "FunStockDigital", color: Color.SecondaryText }, + "23": { name: "GameBillet", color: Color.SecondaryText }, + "24": { name: "Voidu", color: Color.SecondaryText }, + "25": { name: "Epic Games Store", color: Color.SecondaryText }, + "26": { name: "Razer Game Store", color: Color.SecondaryText }, + "27": { name: "Gamesplanet", color: Color.SecondaryText }, + "28": { name: "Gamesload", color: Color.SecondaryText }, + "29": { name: "2Game", color: Color.SecondaryText }, + "30": { name: "IndieGala", color: Color.SecondaryText }, + "31": { name: "Blizzard Shop", color: Color.SecondaryText }, + "32": { name: "AllYouPlay", color: Color.SecondaryText }, + "33": { name: "DLGamer", color: Color.SecondaryText }, + "34": { name: "Noctre", color: Color.SecondaryText }, + "35": { name: "DreamGame", color: Color.SecondaryText }, +}; + +const CHEAPSHARK_MAP: Record = { + "1": "steam", + "2": "gamersgate", + "3": "gmg", + "7": "gog", + "8": "ea", + "11": "humble", + "13": "ubisoft", + "15": "fanatical", + "21": "wingamestore", + "23": "gamebillet", + "24": "voidu", + "25": "epic", + "27": "gamesplanet", + "29": "2game", + "30": "indiegala", + "31": "blizzard", + "32": "allyouplay", + "33": "dlgamer", +}; + +const cache = new Cache(); +export default function TopDeals() { + const [deals, setDeals] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedStores, setSelectedStores] = useState(null); + + const preferences = getPreferenceValues(); + const minDiscount = preferences.minDiscount || "0"; + const maxPrice = preferences.maxPrice || "50"; + + useEffect(() => { + LocalStorage.getItem("selected_stores").then((stored) => { + if (stored) setSelectedStores(JSON.parse(stored)); + else setSelectedStores(["all"]); + }); + }, []); + + useEffect(() => { + const fetchDeals = async () => { + setIsLoading(true); + const cacheKey = `top_deals_${minDiscount}_${maxPrice}`; + const cachedData = cache.get(cacheKey); + + if (cachedData) { + const parsedData = JSON.parse(cachedData); + const cacheAge = Date.now() - parsedData.timestamp; + if (cacheAge < 60 * 60 * 1000) { + setDeals(parsedData.deals); + setIsLoading(false); + return; + } + } + + try { + const response = await fetch( + `https://www.cheapshark.com/api/1.0/deals?upperPrice=${maxPrice}&onSale=1&sortBy=Deal%20Rating`, + ); + const data = await response.json(); + data.sort((a: any, b: any) => { + const ratingDiff = + parseFloat(b.dealRating) - parseFloat(a.dealRating); + if (ratingDiff !== 0) return ratingDiff; + const savingsDiff = parseFloat(b.savings) - parseFloat(a.savings); + if (savingsDiff !== 0) return savingsDiff; + return parseFloat(b.salePrice) - parseFloat(a.salePrice); + }); + const filteredByDiscount = data.filter( + (deal: any) => parseFloat(deal.savings) >= parseFloat(minDiscount), + ); + + cache.set( + cacheKey, + JSON.stringify({ timestamp: Date.now(), deals: filteredByDiscount }), + ); + setDeals(filteredByDiscount); + } catch (error) { + console.error("Error fetching deals:", error); + } finally { + setIsLoading(false); + } + }; + + fetchDeals(); + }, [minDiscount, maxPrice]); + + const filteredDeals = deals.filter((deal: any) => { + if (selectedStores === null) return false; + if (selectedStores.includes("all")) return true; + const mappedId = CHEAPSHARK_MAP[deal.storeID] || "other"; + return selectedStores.includes(mappedId); + }); + + return ( + + {filteredDeals.map((deal) => { + const store = STORES[deal.storeID] || { + name: "Unknown", + color: Color.SecondaryText, + }; + const discount = Math.round(parseFloat(deal.savings)); + + return ( + + + + + {deal.metacriticLink && ( + <> + + + + )} + + } + /> + ); + })} + + ); +} diff --git a/extensions/game-scout/src/utils.ts b/extensions/game-scout/src/utils.ts new file mode 100644 index 00000000000..ed6a8ec2cd4 --- /dev/null +++ b/extensions/game-scout/src/utils.ts @@ -0,0 +1,111 @@ +export const STORE_MAP: Record = { + steam: ["Steam"], + epic: ["Epic Games Store"], + gog: ["GOG"], + humble: ["Humble Store", "Humble Widget"], + fanatical: ["Fanatical"], + gmg: ["Green Man Gaming"], + ea: ["Origin", "EA App", "EA Store"], + ubisoft: ["Ubisoft Store", "Uplay"], + blizzard: ["Blizzard Shop", "Battle.net"], + microsoft: ["Microsoft Store", "Xbox Store"], + gamersgate: ["GamersGate"], + indiegala: ["IndieGala Store", "IndieGala"], + dlgamer: ["DLGamer"], + gamebillet: ["GameBillet"], + voidu: ["Voidu"], + gamesplanet: [ + "GamesPlanet US", + "GamesPlanet UK", + "GamesPlanet FR", + "GamesPlanet DE", + ], + wingamestore: ["WinGameStore", "MacGameStore"], + "2game": ["2Game"], + allyouplay: ["AllYouPlay"], + etailmarket: ["eTail.Market"], + joybuggy: ["JoyBuggy"], + planetplay: ["PlanetPlay"], + other: [], +}; + +export const STORE_LOOKUP: Record = {}; +for (const [id, names] of Object.entries(STORE_MAP)) { + names.forEach((name) => { + STORE_LOOKUP[name] = id; + }); +} + +export function formatPrice( + amount: number | undefined, + currency: string | undefined, +): string { + if (amount === undefined || amount === null) return "-"; + if (amount === 0) return "FREE"; + const symbols: Record = { + USD: "$", + GBP: "ยฃ", + EUR: "โ‚ฌ", + TRY: "โ‚บ", + JPY: "ยฅ", + CNY: "ยฅ", + KRW: "โ‚ฉ", + INR: "โ‚น", + RUB: "โ‚ฝ", + BRL: "R$", + CAD: "CA$", + AUD: "A$", + NZD: "NZ$", + HKD: "HK$", + SGD: "S$", + MXN: "MX$", + NOK: "kr", + SEK: "kr", + DKK: "kr", + PLN: "zล‚", + CZK: "Kฤ", + HUF: "Ft", + CHF: "CHF", + ZAR: "R", + SAR: "๏ทผ", + AED: "ุฏ.ุฅ", + QAR: "๏ทผ", + THB: "เธฟ", + IDR: "Rp", + MYR: "RM", + PHP: "โ‚ฑ", + TWD: "NT$", + ARS: "ARS$", + CLP: "CLP$", + COP: "COP$", + UYU: "UYU$", + KZT: "โ‚ธ", + ILS: "โ‚ช", + UAH: "โ‚ด", + }; + const curr = currency || "USD"; + const symbol = symbols[curr] || curr + " "; + const noDecimals = ["JPY", "KRW"]; + return `${symbol}${noDecimals.includes(curr) ? Math.round(amount) : amount.toFixed(2)}`; +} + +export function isStoreAllowed( + shopName: string, + selectedStores: string[], +): boolean { + if ( + selectedStores.length === 0 || + (selectedStores.length === 1 && selectedStores[0] === "all") + ) + return true; + return selectedStores.includes(STORE_LOOKUP[shopName] || "other"); +} + +export function getTimeContext(timestamp: number): string { + const days = Math.floor((Date.now() - timestamp) / (1000 * 60 * 60 * 24)); + if (days === 0) return "today"; + if (days < 30) return `${days} days ago`; + const months = Math.floor(days / 30); + if (months < 12) return `${months} months ago`; + return `${(days / 365).toFixed(1)} years ago`; +} diff --git a/extensions/game-scout/tsconfig.json b/extensions/game-scout/tsconfig.json new file mode 100644 index 00000000000..fe886e6dfaa --- /dev/null +++ b/extensions/game-scout/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "target": "es2022", + "lib": ["es2022", "dom"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "isolatedModules": true + }, + "include": ["src"] +} \ No newline at end of file