🏷️ Search for custom tailnet name offers with keywords.

feat: initial commit

Chloe 482bfd8c

.github/assets/1_home.png

This is a binary file and will not be displayed.

.github/assets/2_words.png

This is a binary file and will not be displayed.

.github/assets/3_offers.png

This is a binary file and will not be displayed.

.github/assets/icon.png

This is a binary file and will not be displayed.

+26
.gitignore
···
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + .output 12 + stats.html 13 + stats-*.json 14 + .wxt 15 + web-ext.config.ts 16 + 17 + # Editor directories and files 18 + .vscode/* 19 + !.vscode/extensions.json 20 + .idea 21 + .DS_Store 22 + *.suo 23 + *.ntvs* 24 + *.njsproj 25 + *.sln 26 + *.sw?
+16
LICENSE
···
··· 1 + zlib License 2 + 3 + (C) 2025 Sapphic Angels 4 + 5 + This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable 6 + for any damages arising from the use of this software. 7 + 8 + Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it 9 + and redistribute it freely, subject to the following restrictions: 10 + 11 + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If 12 + you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not 13 + required. 14 + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original 15 + software. 16 + 3. This notice may not be removed or altered from any source distribution.
+48
README.md
···
··· 1 + <div align="center"> 2 + 3 + # <img align="top" src="assets/icon.png" alt="tailname icon" width="40" /> tailname 4 + 5 + [![Formatted with Biome](https://img.shields.io/badge/Formatted_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev/) 6 + [![Linted with Biome](https://img.shields.io/badge/Linted_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev) 7 + [![License](https://img.shields.io/github/license/SapphoSys/tailname?labelColor=black&color=#3f5db3)](https://github.com/SapphoSys/tailname/blob/master/LICENSE) 8 + 9 + A browser extension that finds custom tailnet name offers for your Tailscale account using keywords. 10 + </div> 11 + 12 + <div style="display: flex; justify-content: center; gap: 1em;"> 13 + <img src=".github/assets/1_home.png" alt="Home page" width="250" /> 14 + <img src=".github/assets/2_words.png" alt="Words screen" width="250" /> 15 + <img src=".github/assets/3_offers.png" alt="Offers screen" width="250" /> 16 + </div> 17 + 18 + ## How to install 19 + ### Chromium-based browsers (Google Chrome, Microsoft Edge, etc) 20 + *We plan on submitting this extension to the Chrome Web Store soon.* 21 + 22 + In the meantime, use the following instructions to sideload the extension: 23 + 24 + 0. Get the [latest release for Chromium](link). 25 + 1. Extract the ZIP archive using an archival program of your choice. 26 + 2. Navigate to the Extensions page using [chrome://extensions](chrome://extensions). 27 + 3. Click the "Load unpacked" button. 28 + 4. Select the extracted folder of the extension. 29 + 5. Done! 30 + 31 + ### Gecko-based browsers (Firefox, Zen Browser, etc) 32 + *The extension submission is currently awaiting a review on the Mozilla Add-on Developer Hub.* 33 + 34 + In the meantime, use the following instructions to sideload the extension: 35 + 36 + 0. Get the [latest release for Firefox](link). 37 + 1. Navigate to the Extensions page using [about:addons](about:addons). 38 + 2. Click on the settings icon, and select "Install Add-ons From File...". 39 + 3. Select the ZIP archive of the extension. 40 + 4. Done! 41 + 42 + ## License 43 + 44 + This repository is licensed under the [zlib](LICENSE) license. 45 + 46 + This extension is not affiliated with Tailscale, Inc. 47 + 48 + © 2025 Sapphic Angels.
assets/icon.png

This is a binary file and will not be displayed.

+48
biome.json
···
··· 1 + { 2 + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", 3 + "vcs": { 4 + "enabled": false, 5 + "clientKind": "git", 6 + "useIgnoreFile": false 7 + }, 8 + "files": { 9 + "includes": [ 10 + "entrypoints/**/*", 11 + "handlers/**/*", 12 + "helpers/**/*", 13 + "types/**/*", 14 + "biome.json", 15 + "config.ts", 16 + "tailwind.config.ts", 17 + "tsconfig.json", 18 + "wxt.config.ts" 19 + ], 20 + "ignoreUnknown": false 21 + }, 22 + "formatter": { 23 + "enabled": true, 24 + "indentStyle": "tab" 25 + }, 26 + "linter": { 27 + "enabled": true, 28 + "rules": { 29 + "recommended": true, 30 + "suspicious": { 31 + "noExplicitAny": "error" 32 + } 33 + } 34 + }, 35 + "javascript": { 36 + "formatter": { 37 + "quoteStyle": "single" 38 + } 39 + }, 40 + "assist": { 41 + "enabled": true, 42 + "actions": { 43 + "source": { 44 + "organizeImports": "on" 45 + } 46 + } 47 + } 48 + }
+12
config.ts
···
··· 1 + export const badgeColors = { 2 + active: '#3f5eb3', 3 + inactive: '#1f1e1e', 4 + }; 5 + export const badgeUpdateInterval = 30; 6 + export const defaultCheckOffersInterval = 30; 7 + export const eligibilityCacheInterval = 60; 8 + export const tokenValidityInterval = 5 * 60; 9 + export const tailscaleWordsUrls = [ 10 + 'https://raw.githubusercontent.com/tailscale/tailscale/refs/heads/main/words/tails.txt', 11 + 'https://raw.githubusercontent.com/tailscale/tailscale/refs/heads/main/words/scales.txt', 12 + ];
+50
entrypoints/background/alarms.ts
···
··· 1 + import { updateBadgeText } from '$background/badge'; 2 + import { badgeUpdateInterval } from '$config'; 3 + import { log } from '$helpers/logging'; 4 + import { checkTailscaleOffers } from '$helpers/offers'; 5 + import { filterValidTokens } from '$helpers/tokens'; 6 + 7 + export const setupAlarms = () => { 8 + browser.alarms.create('badgeUpdateAlarm', { 9 + periodInMinutes: badgeUpdateInterval / 60, 10 + }); 11 + 12 + browser.alarms.onAlarm.addListener((alarm) => { 13 + log(`Alarm "${alarm.name}" triggered.`, 'DEBUG', 'Alarms'); 14 + 15 + if (alarm.name === 'checkOffersAlarm') { 16 + checkTailscaleOffers(); 17 + updateBadgeText(); 18 + } 19 + 20 + if (alarm.name === 'badgeUpdateAlarm') { 21 + log( 22 + 'Updating the badge counter and cleaning expired offers...', 23 + 'INFO', 24 + 'Alarms', 25 + ); 26 + 27 + browser.storage.local.get(['tailscaleTokens'], (result) => { 28 + const now = Date.now(); 29 + const validTokens = filterValidTokens( 30 + result.tailscaleTokens || [], 31 + now, 32 + ); 33 + 34 + if (validTokens.length !== (result.tailscaleTokens || []).length) { 35 + browser.storage.local.set({ tailscaleTokens: validTokens }, () => { 36 + browser.runtime.sendMessage({ 37 + action: 'tokensUpdated', 38 + tokens: validTokens, 39 + }); 40 + log('Expired offers removed.', 'INFO', 'Alarms'); 41 + updateBadgeText(); 42 + }); 43 + } else { 44 + // Update the badges anyway, even if no offers were removed 45 + updateBadgeText(); 46 + } 47 + }); 48 + } 49 + }); 50 + };
+30
entrypoints/background/badge.ts
···
··· 1 + import { badgeColors } from '$config'; 2 + import { log } from '$helpers/logging'; 3 + import type { Token } from '$types/tokens'; 4 + 5 + export const updateBadgeText = async () => { 6 + try { 7 + const result = await browser.storage.local.get(['tailscaleTokens']); 8 + const tokens: Token[] = Array.isArray(result.tailscaleTokens) 9 + ? result.tailscaleTokens 10 + : []; 11 + const validTokens = tokens.filter((t) => t?.token); 12 + const count = validTokens.length; 13 + 14 + const badgeText = count > 0 ? count.toString() : '0'; 15 + const badgeColor = count > 0 ? badgeColors.active : badgeColors.inactive; 16 + 17 + if (browser.action) { 18 + await browser.action.setBadgeText({ text: badgeText }); 19 + await browser.action.setBadgeBackgroundColor({ color: badgeColor }); 20 + } else if (browser.browserAction) { 21 + await browser.browserAction.setBadgeText({ text: badgeText }); 22 + await browser.browserAction.setBadgeBackgroundColor({ 23 + color: badgeColor, 24 + }); 25 + } 26 + } catch (error) { 27 + const errMsg = error instanceof Error ? error.message : 'Unknown error'; 28 + log(`Failed to update badge text: ${errMsg}`); 29 + } 30 + };
+18
entrypoints/background/cookies.ts
···
··· 1 + import { log } from '$helpers/logging'; 2 + 3 + export const setupCookies = () => { 4 + browser.cookies.onChanged.addListener((changeInfo) => { 5 + if ( 6 + changeInfo.cookie?.domain.includes('tailscale.com') && 7 + changeInfo.cookie.name === 'tailcontrol' 8 + ) { 9 + log( 10 + 'Detected change to Tailscale login cookie. Now refreshing eligibility status...', 11 + 'INFO', 12 + 'Cookies', 13 + ); 14 + browser.storage.local.remove('eligibility'); 15 + browser.runtime.sendMessage({ action: 'refreshEligibility' }); 16 + } 17 + }); 18 + };
+23
entrypoints/background/index.ts
···
··· 1 + import { setupAlarms } from '$background/alarms'; 2 + import { updateBadgeText } from '$background/badge'; 3 + import { setupCookies } from '$background/cookies'; 4 + import { setupMessaging } from '$background/messaging'; 5 + import { setupNotifications } from '$background/notifications'; 6 + import { setupStorage } from '$background/storage'; 7 + import { log } from '$helpers/logging'; 8 + 9 + export default defineBackground(() => { 10 + log('Setting up alarms and event listeners...', 'DEBUG', 'Setup'); 11 + 12 + // Update badge on startup 13 + updateBadgeText(); 14 + 15 + // Setup listeners 16 + setupAlarms(); 17 + setupCookies(); 18 + setupMessaging(); 19 + setupNotifications(); 20 + setupStorage(); 21 + 22 + log('Background setup complete.', 'DEBUG', 'Setup'); 23 + });
+23
entrypoints/background/messaging.ts
···
··· 1 + import handlers from '$handlers'; 2 + import { log } from '$helpers/logging'; 3 + import type { Message } from '$types/messages'; 4 + 5 + export const setupMessaging = () => { 6 + browser.runtime.onMessage.addListener( 7 + (message: Message, _sender, sendResponse) => { 8 + const handler = handlers[message.action]; 9 + if (handler) { 10 + handler(message, sendResponse); 11 + return true; 12 + } 13 + 14 + log( 15 + `No handler found for action: ${message.action}`, 16 + 'WARNING', 17 + 'Messaging', 18 + ); 19 + 20 + return false; 21 + }, 22 + ); 23 + };
+21
entrypoints/background/notifications.ts
···
··· 1 + import { log } from '$helpers/logging'; 2 + 3 + export const setupNotifications = () => { 4 + browser.runtime.onInstalled.addListener(() => { 5 + browser.storage.local.get(['useSystemPush', 'useNtfyPush'], (result) => { 6 + const updates: Record<string, boolean> = {}; 7 + if (typeof result.useSystemPush !== 'boolean') 8 + updates.useSystemPush = true; 9 + if (typeof result.useNtfyPush !== 'boolean') updates.useNtfyPush = false; 10 + if (Object.keys(updates).length > 0) { 11 + browser.storage.local.set(updates, () => { 12 + log( 13 + 'Initialized default notification settings in storage.', 14 + 'DEBUG', 15 + 'Storage', 16 + ); 17 + }); 18 + } 19 + }); 20 + }); 21 + };
+9
entrypoints/background/storage.ts
···
··· 1 + import { updateBadgeText } from '$background/badge'; 2 + 3 + export const setupStorage = () => { 4 + browser.storage.onChanged.addListener((changes) => { 5 + if (changes.tailscaleTokens) { 6 + updateBadgeText(); 7 + } 8 + }); 9 + };
+39
entrypoints/content.ts
···
··· 1 + export default defineContentScript({ 2 + matches: ['https://login.tailscale.com/admin/dns*'], 3 + main() { 4 + browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { 5 + if (message.action === 'claimToken') { 6 + const { tcd, token } = message; 7 + fetch('https://login.tailscale.com/admin/api/tcd', { 8 + credentials: 'include', 9 + headers: { 10 + Accept: 'application/json, text/plain, */*', 11 + 'Accept-Language': 'en-US,en;q=0.5', 12 + 'Content-Type': 'application/json', 13 + 'Sec-Fetch-Site': 'same-origin', 14 + }, 15 + referrer: 'https://login.tailscale.com/admin/dns', 16 + body: JSON.stringify({ tcd, token }), 17 + method: 'POST', 18 + mode: 'cors', 19 + }) 20 + .then((res) => res.json()) 21 + .then((result) => { 22 + if (result.error === 'invalid tailnet name offer token') { 23 + sendResponse({ 24 + success: false, 25 + error: 'This tailnet token offer has expired.', 26 + }); 27 + return; 28 + } else { 29 + sendResponse({ success: true, result }); 30 + } 31 + }) 32 + .catch((err) => { 33 + sendResponse({ success: false, error: err.toString() }); 34 + }); 35 + return true; 36 + } 37 + }); 38 + }, 39 + });
+200
entrypoints/popup/App.tsx
···
··· 1 + import { type FC, useEffect, useState } from 'react'; 2 + 3 + import AlertModal from '$components/AlertModal'; 4 + import EligibilityModal from '$components/EligibilityModal'; 5 + import Footer from '$components/Footer'; 6 + import LoadingOverlay from '$components/LoadingOverlay'; 7 + 8 + import { useEligibility } from '$hooks/useEligibility'; 9 + import { useInputValidation } from '$hooks/useInputValidation'; 10 + import { useModal } from '$hooks/useModal'; 11 + import { useStatus } from '$hooks/useStatus'; 12 + import { useTailnetNames } from '$hooks/useTailnetNames'; 13 + import { useTimer } from '$hooks/useTimer'; 14 + import { useTokens } from '$hooks/useTokens'; 15 + import { useWords } from '$hooks/useWords'; 16 + 17 + import MainScreen from '$screens/MainScreen'; 18 + import TailnetWordsScreen from '$screens/TailnetWords'; 19 + import TokenListScreen from '$screens/TokenList'; 20 + import WordListScreen from '$screens/WordList'; 21 + 22 + const App: FC = () => { 23 + const [inputValue, setInputValue] = useState(''); 24 + const [error, _setError] = useState<string | null>(null); 25 + const [showAlert, setShowAlert] = useState(false); 26 + const [alertMessage, setAlertMessage] = useState(''); 27 + const [claimedToken, setClaimedToken] = useState<string | null>(null); 28 + const [screen, setScreen] = useState< 29 + 'main' | 'words' | 'tokens' | 'wordlist' 30 + >('main'); 31 + const [isTransitioning, setIsTransitioning] = useState(false); 32 + 33 + const handleShowWordsScreen = () => { 34 + setIsTransitioning(true); 35 + setScreen('words'); 36 + }; 37 + const handleShowTokensScreen = () => { 38 + setIsTransitioning(true); 39 + setScreen('tokens'); 40 + }; 41 + const handleBackToWords = () => { 42 + setIsTransitioning(true); 43 + setScreen('words'); 44 + }; 45 + const handleBackToMain = () => { 46 + setIsTransitioning(true); 47 + setScreen('main'); 48 + }; 49 + const handleShowWordListScreen = () => { 50 + setIsTransitioning(true); 51 + setScreen('wordlist'); 52 + }; 53 + 54 + const { status, setStatus, timer, loading, setLoading } = useTimer(); 55 + const { handleStart, handleStop } = useStatus(setStatus); 56 + const { tails, scales } = useWords(); 57 + const { 58 + tailnetNames, 59 + setTailnetNames, 60 + handleAddTailnet, 61 + handleRemoveTailnet, 62 + } = useTailnetNames(tails, scales, setStatus, inputValue, setInputValue); 63 + const [ 64 + tokens, 65 + setTokens, 66 + { handleClaimToken, handleRemoveToken, error: claimTokenError }, 67 + ] = useTokens({ 68 + setLoading, 69 + setAlertMessage, 70 + setShowAlert, 71 + setClaimedToken, 72 + }); 73 + 74 + const { eligibility, eligibilityLoading } = useEligibility(); 75 + const { alertModalRef, alertCloseBtnRef, handleAlertCloseModal } = useModal( 76 + showAlert, 77 + setShowAlert, 78 + setAlertMessage, 79 + claimedToken, 80 + setClaimedToken, 81 + setTokens, 82 + setTailnetNames, 83 + ); 84 + const isInputValid = useInputValidation( 85 + inputValue, 86 + tails, 87 + scales, 88 + tailnetNames, 89 + ); 90 + 91 + useEffect(() => { 92 + if (isTransitioning) { 93 + const timer = setTimeout(() => setIsTransitioning(false), 300); 94 + return () => clearTimeout(timer); 95 + } 96 + }, [isTransitioning]); 97 + 98 + return ( 99 + <div className="flex flex-col min-w-[600px] max-w-lg min-h-[600px] bg-background overflow-hidden mx-auto h-full"> 100 + <div className="flex-1 relative flex flex-col h-full"> 101 + <div className="flex-1 relative overflow-x-hidden overflow-y-auto [&::-webkit-scrollbar]:hidden"> 102 + <div 103 + className={`absolute inset-0 transition-all duration-500 bg-background ${ 104 + screen === 'main' 105 + ? 'translate-x-0 opacity-100 pointer-events-auto' 106 + : 'translate-x-[-100%] opacity-0 pointer-events-none' 107 + }`} 108 + > 109 + <MainScreen 110 + onShowWords={handleShowWordsScreen} 111 + onShowTokens={handleShowTokensScreen} 112 + status={status} 113 + timer={timer} 114 + handleStart={handleStart} 115 + handleStop={handleStop} 116 + tailnetNames={tailnetNames} 117 + tokens={tokens} 118 + /> 119 + </div> 120 + 121 + <div 122 + className={`absolute inset-0 transition-all duration-500 bg-background ${ 123 + screen === 'words' 124 + ? 'translate-x-0 opacity-100 pointer-events-auto' 125 + : screen === 'main' 126 + ? 'translate-x-[100%] opacity-0 pointer-events-none' 127 + : 'translate-x-[-100%] opacity-0 pointer-events-none' 128 + }`} 129 + > 130 + <TailnetWordsScreen 131 + tailnetNames={tailnetNames} 132 + handleAddTailnet={handleAddTailnet} 133 + handleRemoveTailnet={handleRemoveTailnet} 134 + inputValue={inputValue} 135 + setInputValue={setInputValue} 136 + isInputValid={!!isInputValid} 137 + loading={loading} 138 + onBack={handleBackToMain} 139 + error={error} 140 + tails={tails} 141 + scales={scales} 142 + onShowWordList={handleShowWordListScreen} 143 + /> 144 + </div> 145 + 146 + <div 147 + className={`absolute inset-0 transition-all duration-500 bg-background ${ 148 + screen === 'tokens' 149 + ? 'translate-x-0 opacity-100 pointer-events-auto' 150 + : 'translate-x-[100%] opacity-0 pointer-events-none' 151 + }`} 152 + > 153 + <TokenListScreen 154 + tokens={tokens} 155 + handleClaimToken={handleClaimToken} 156 + handleRemoveToken={handleRemoveToken} 157 + loading={loading} 158 + error={claimTokenError} 159 + onBack={handleBackToMain} 160 + /> 161 + </div> 162 + 163 + <div 164 + className={`absolute inset-0 transition-all duration-500 bg-background ${ 165 + screen === 'wordlist' 166 + ? 'translate-x-0 opacity-100 pointer-events-auto' 167 + : 'translate-x-[100%] opacity-0 pointer-events-none' 168 + }`} 169 + > 170 + <WordListScreen 171 + tails={tails} 172 + scales={scales} 173 + onBack={handleBackToWords} 174 + /> 175 + </div> 176 + 177 + <LoadingOverlay 178 + eligibilityLoading={eligibilityLoading} 179 + loading={loading} 180 + /> 181 + <EligibilityModal 182 + eligibility={eligibility} 183 + eligibilityLoading={eligibilityLoading} 184 + /> 185 + <AlertModal 186 + alertCloseBtnRef={alertCloseBtnRef} 187 + alertMessage={alertMessage} 188 + alertModalRef={alertModalRef} 189 + handleAlertCloseModal={handleAlertCloseModal} 190 + showAlert={showAlert} 191 + /> 192 + </div> 193 + 194 + <Footer /> 195 + </div> 196 + </div> 197 + ); 198 + }; 199 + 200 + export default App;
+27
entrypoints/popup/components/ActionCard.tsx
···
··· 1 + import type { FC, ReactNode } from 'react'; 2 + 3 + interface ActionCardProps { 4 + icon: ReactNode; 5 + title: string; 6 + subtitle: string; 7 + onClick: () => void; 8 + } 9 + 10 + const ActionCard: FC<ActionCardProps> = ({ 11 + icon, 12 + title, 13 + subtitle, 14 + onClick, 15 + }) => ( 16 + <button 17 + className="bg-header shadow-lg rounded-xl p-6 flex flex-col items-center justify-center hover:scale-105 transition-transform border border-border w-full mx-auto" 18 + type="button" 19 + onClick={onClick} 20 + > 21 + <span className="text-2xl mb-2">{icon}</span> 22 + <span className="text-base font-semibold mb-1 text-text">{title}</span> 23 + <span className="text-subtext text-sm">{subtitle}</span> 24 + </button> 25 + ); 26 + 27 + export default ActionCard;
+57
entrypoints/popup/components/AlertModal.tsx
···
··· 1 + import type { FC, RefObject } from 'react'; 2 + import { useId } from 'react'; 3 + 4 + interface AlertModalProps { 5 + showAlert: boolean; 6 + alertMessage: string; 7 + alertModalRef: RefObject<HTMLDivElement | null>; 8 + alertCloseBtnRef: RefObject<HTMLButtonElement | null>; 9 + handleAlertCloseModal: () => void; 10 + } 11 + const AlertModal: FC<AlertModalProps> = ({ 12 + showAlert, 13 + alertMessage, 14 + alertModalRef, 15 + alertCloseBtnRef, 16 + handleAlertCloseModal, 17 + }) => { 18 + const alertMessageId = useId(); 19 + 20 + return ( 21 + <div 22 + ref={alertModalRef} 23 + tabIndex={-1} 24 + className={`fixed inset-0 z-50 flex items-center justify-center bg-background bg-opacity-40 backdrop-blur-sm transition-opacity duration-300 ${ 25 + showAlert ? 'opacity-100' : 'opacity-0 pointer-events-none' 26 + }`} 27 + role="dialog" 28 + aria-modal="true" 29 + > 30 + <div 31 + className={`rounded-md shadow-2xl p-6 bg-header max-w-md mx-auto flex flex-col border text-text border-border transform transition-all duration-300 ${ 32 + showAlert ? 'scale-100 opacity-100' : 'scale-95 opacity-0' 33 + }`} 34 + tabIndex={-1} 35 + > 36 + <span id={alertMessageId} className="block mb-4 text-lg text-gray-800"> 37 + {alertMessage.split('\n').map((line) => ( 38 + <span key={line} className="block mb-3"> 39 + {line} 40 + </span> 41 + ))} 42 + </span> 43 + 44 + <button 45 + ref={alertCloseBtnRef as RefObject<HTMLButtonElement>} 46 + className="px-4 py-2 bg-accent text-text rounded-md hover:opacity-80 transition-opacity focus:outline-none" 47 + type="button" 48 + onClick={handleAlertCloseModal} 49 + > 50 + OK 51 + </button> 52 + </div> 53 + </div> 54 + ); 55 + }; 56 + 57 + export default AlertModal;
+60
entrypoints/popup/components/ConfirmLogoutModal.tsx
···
··· 1 + import type { FC } from 'react'; 2 + 3 + interface ConfirmLogoutModalProps { 4 + show: boolean; 5 + onConfirm: () => void; 6 + onCancel: () => void; 7 + } 8 + 9 + const ConfirmLogoutModal: FC<ConfirmLogoutModalProps> = ({ 10 + show, 11 + onConfirm, 12 + onCancel, 13 + }) => { 14 + // Animate modal in/out 15 + return ( 16 + <div 17 + className={`fixed inset-0 flex items-center text-text justify-center bg-background bg-opacity-40 backdrop-blur-sm z-50 transition-opacity duration-300 ${ 18 + show ? 'opacity-100' : 'opacity-0 pointer-events-none' 19 + }`} 20 + > 21 + <div 22 + className={`rounded-md shadow-2xl p-6 bg-header max-w-md mx-auto flex flex-col border border-border transform transition-all duration-300 ${ 23 + show ? 'scale-100 opacity-100' : 'scale-95 opacity-0' 24 + }`} 25 + > 26 + <h2 className="text-2xl font-bold mb-2">Log out</h2> 27 + <div className="flex flex-col gap-2 pb-3"> 28 + <p className="text-base"> 29 + This will log you out from your account. Are you sure? 30 + </p> 31 + 32 + <p className="text-sm text-subtext"> 33 + You will need to log in again with a different Tailscale account to 34 + use the extension. 35 + </p> 36 + </div> 37 + 38 + <div className="flex gap-2"> 39 + <button 40 + className="px-4 py-2 rounded-lg bg-danger text-text font-medium hover:opacity-80 transition-opacity" 41 + onClick={onConfirm} 42 + type="button" 43 + > 44 + Log out 45 + </button> 46 + 47 + <button 48 + className="px-4 py-2 rounded-lg bg-item text-text font-medium hover:opacity-80 transition-opacity" 49 + onClick={onCancel} 50 + type="button" 51 + > 52 + Cancel 53 + </button> 54 + </div> 55 + </div> 56 + </div> 57 + ); 58 + }; 59 + 60 + export default ConfirmLogoutModal;
+125
entrypoints/popup/components/EligibilityModal.tsx
···
··· 1 + import { type FC, useEffect, useState } from 'react'; 2 + import ConfirmLogoutModal from '$components/ConfirmLogoutModal'; 3 + import type { Eligibility } from '$types/eligibility'; 4 + 5 + interface EligibilityModalProps { 6 + eligibility: Eligibility | null; 7 + eligibilityLoading: boolean; 8 + } 9 + 10 + const EligibilityModal: FC<EligibilityModalProps> = ({ 11 + eligibility, 12 + eligibilityLoading, 13 + }) => { 14 + const [showConfirmLogout, setShowConfirmLogout] = useState(false); 15 + const [visible, setVisible] = useState(false); 16 + const [shouldRender, setShouldRender] = useState(false); 17 + 18 + // Animate modal in/out 19 + useEffect(() => { 20 + if (!eligibilityLoading && eligibility && !eligibility.eligible) { 21 + setShouldRender(true); 22 + setTimeout(() => setVisible(true), 10); // pop in 23 + } else if (visible) { 24 + setVisible(false); 25 + setTimeout(() => setShouldRender(false), 300); // fade out 26 + } else { 27 + setShouldRender(false); 28 + } 29 + }, [eligibilityLoading, eligibility, visible]); 30 + 31 + const handleLogoutClick = (e: React.MouseEvent) => { 32 + e.preventDefault(); 33 + setShowConfirmLogout(true); 34 + }; 35 + const handleConfirmLogout = () => { 36 + setShowConfirmLogout(false); 37 + window.open('https://login.tailscale.com/logout', '_blank'); 38 + browser.storage.local.remove('eligibility'); 39 + }; 40 + const handleCancelLogout = () => { 41 + setShowConfirmLogout(false); 42 + }; 43 + if (!shouldRender || !eligibility) return null; 44 + return ( 45 + <div 46 + className={`fixed inset-0 flex items-center justify-center bg-background bg-opacity-40 backdrop-blur-sm shadow-[#000] transition-opacity duration-300 ${ 47 + visible ? 'opacity-100' : 'opacity-0 pointer-events-none' 48 + }`} 49 + aria-modal="true" 50 + role="dialog" 51 + > 52 + <div 53 + className={`text-text shadow-[#000] rounded-md shadow-2xl p-6 bg-header max-w-lg mx-auto flex flex-col border border-border transform transition-all duration-300 ${ 54 + visible ? 'scale-100 opacity-100' : 'scale-95 opacity-0' 55 + }`} 56 + > 57 + <div className="flex flex-col flex-wrap gap-x-2 mb-2"> 58 + <h2 className="text-2xl font-bold"> 59 + {eligibility.id === 'not-logged-in' 60 + ? 'Not logged in' 61 + : eligibility.id === 'api-error' 62 + ? 'API Error' 63 + : 'Not Eligible'} 64 + </h2> 65 + <p className="text-base text-subtext">({eligibility.id})</p> 66 + </div> 67 + <div className="text-base text-danger mb-2"> 68 + {eligibility.id === 'not-logged-in' 69 + ? 'You must be logged in to Tailscale to use this extension.' 70 + : eligibility.reason} 71 + </div> 72 + <div className="text-sm text-subtext"> 73 + {eligibility.id === 'not-logged-in' 74 + ? 'When you login, the extension will refresh.' 75 + : 'Please log out and try another Tailscale account.'} 76 + </div> 77 + <div className="login-btn-row flex gap-2 mt-4"> 78 + {eligibility.id === 'not-logged-in' ? ( 79 + <a 80 + href="https://login.tailscale.com" 81 + target="_blank" 82 + rel="noopener noreferrer" 83 + > 84 + <button 85 + className="login-btn px-4 py-2 rounded-lg bg-accent text-text font-medium hover:opacity-80 transition-opacity" 86 + type="button" 87 + > 88 + Log in 89 + </button> 90 + </a> 91 + ) : ( 92 + <button 93 + className="login-btn px-4 py-2 rounded-lg bg-danger text-text font-medium hover:opacity-80 transition-opacity" 94 + type="button" 95 + onClick={handleLogoutClick} 96 + > 97 + Log out 98 + </button> 99 + )} 100 + {eligibility.id === 'custom-tailnet' && ( 101 + <a 102 + href="https://tailscale.com/kb/1217/tailnet-name" 103 + target="_blank" 104 + rel="noopener noreferrer" 105 + > 106 + <button 107 + className="login-btn learn-more px-4 py-2 rounded-lg bg-accent text-text font-medium hover:opacity-80 transition-opacity" 108 + type="button" 109 + > 110 + Learn more 111 + </button> 112 + </a> 113 + )} 114 + </div> 115 + </div> 116 + <ConfirmLogoutModal 117 + show={showConfirmLogout} 118 + onConfirm={handleConfirmLogout} 119 + onCancel={handleCancelLogout} 120 + /> 121 + </div> 122 + ); 123 + }; 124 + 125 + export default EligibilityModal;
+103
entrypoints/popup/components/FAQModal.tsx
···
··· 1 + import type { FC } from 'react'; 2 + 3 + interface FAQModalProps { 4 + show: boolean; 5 + onClose: () => void; 6 + } 7 + 8 + const FAQModal: FC<FAQModalProps> = ({ show, onClose }) => { 9 + return ( 10 + <div 11 + className={`fixed inset-0 flex items-center justify-center bg-background bg-opacity-40 backdrop-blur-sm z-50 transition-opacity duration-300 ${ 12 + show ? 'opacity-100' : 'opacity-0 pointer-events-none' 13 + }`} 14 + aria-modal="true" 15 + role="dialog" 16 + onClick={(e) => { 17 + if (e.target === e.currentTarget) onClose(); 18 + }} 19 + onKeyDown={(e) => { 20 + if (e.key === 'Escape') onClose(); 21 + }} 22 + tabIndex={-1} 23 + > 24 + <div 25 + className={`rounded-md shadow-2xl p-6 bg-header max-w-md mx-auto flex flex-col border border-border transform transition-all duration-300 ${ 26 + show ? 'scale-100 opacity-100' : 'scale-95 opacity-0' 27 + }`} 28 + > 29 + <h2 className="text-2xl font-bold text-subtext mb-2">FAQ</h2> 30 + <div className="flex flex-col gap-3 pb-3 text-text mb-2"> 31 + <div className="flex flex-col gap-1"> 32 + <h3 className="text-lg font-semibold">What is tailname?</h3> 33 + 34 + <p className="text-sm"> 35 + tailname is a browser extension that finds custom tailnet name 36 + offers for your Tailscale account. 37 + </p> 38 + </div> 39 + 40 + <div className="flex flex-col gap-1"> 41 + <h3 className="text-lg font-semibold"> 42 + How do I use the extension? 43 + </h3> 44 + 45 + <p className="text-sm"> 46 + You'll need to add at least one keyword in the "Manage tailnet 47 + words" screen. After that, the extension will begin the checking 48 + offers process. 49 + </p> 50 + 51 + <p className="text-sm"> 52 + The extension will notify you if it finds a tailnet name offer 53 + that matches one of your keywords or combos. 54 + </p> 55 + </div> 56 + 57 + <div className="flex flex-col gap-1"> 58 + <h3 className="text-lg font-semibold"> 59 + Why can't I find a tailnet name offer? 60 + </h3> 61 + 62 + <p className="text-sm"> 63 + It's recommended you add multiple words to find offers quicker. 64 + All this extension does is request the Tailscale offers API, and 65 + then filter the results using your keywords and combos. 66 + </p> 67 + </div> 68 + 69 + <div className="flex flex-col gap-1"> 70 + <h3 className="text-lg font-semibold"> 71 + I found a bug! Where can I report it? 72 + </h3> 73 + 74 + <p className="text-sm"> 75 + Please visit{' '} 76 + <a 77 + href="https://github.com/SapphoSys/tailname" 78 + className="text-pink hover:opacity-80 hover:underline transition-opacity" 79 + target="_blank" 80 + rel="noopener" 81 + > 82 + the GitHub repository 83 + </a>{' '} 84 + and file a bug report. 85 + </p> 86 + </div> 87 + </div> 88 + 89 + <div className="flex gap-2"> 90 + <button 91 + className="px-4 py-2 rounded-lg bg-accent text-text font-medium hover:opacity-80 transition-opacity" 92 + type="button" 93 + onClick={onClose} 94 + > 95 + Close 96 + </button> 97 + </div> 98 + </div> 99 + </div> 100 + ); 101 + }; 102 + 103 + export default FAQModal;
+53
entrypoints/popup/components/Footer.tsx
···
··· 1 + import { useState } from 'react'; 2 + import { BiSolidHeart } from 'react-icons/bi'; 3 + import FAQModal from './FAQModal'; 4 + 5 + const Footer = () => { 6 + const [showFAQ, setShowFAQ] = useState(false); 7 + return ( 8 + <footer className="flex items-center justify-between p-2 px-4 bg-header border-t-2 border-border text-subtext"> 9 + <p className="flex flex-row gap-1 text-sm items-center"> 10 + <BiSolidHeart size={20} className="fill-pink" aria-hidden={true} />{' '} 11 + <span className="text-text">tailname</span> is made by{' '} 12 + <a 13 + href="https://sapphic.moe" 14 + className="text-pink hover:underline hover:opacity-80" 15 + target="_blank" 16 + rel="noopener" 17 + > 18 + Chloe Arciniega 19 + </a> 20 + </p> 21 + 22 + <div className="flex flex-row gap-2"> 23 + <button 24 + className="bg-item border border-border text-text font-medium text-sm px-3 py-1.5 rounded-lg hover:bg-accent hover:border-accent hover:text-text transition-colors" 25 + onClick={() => setShowFAQ(true)} 26 + type="button" 27 + > 28 + FAQ 29 + </button> 30 + <a 31 + className="bg-item border border-border text-text font-medium text-sm px-3 py-1.5 rounded-lg hover:bg-accent hover:text-text hover:border-accent transition-colors" 32 + href="https://ko-fi.com/solelychloe" 33 + target="_blank" 34 + rel="noopener" 35 + > 36 + Donate 37 + </a> 38 + <a 39 + className="bg-item border border-border text-text font-medium text-sm px-3 py-1.5 rounded-lg hover:bg-accent hover:border-accent hover:text-text transition-colors" 40 + href="https://github.com/SapphoSys/tailname" 41 + target="_blank" 42 + rel="noopener" 43 + > 44 + Source 45 + </a> 46 + </div> 47 + 48 + <FAQModal show={showFAQ} onClose={() => setShowFAQ(false)} /> 49 + </footer> 50 + ); 51 + }; 52 + 53 + export default Footer;
+14
entrypoints/popup/components/HeaderSection.tsx
···
··· 1 + import type { FC } from 'react'; 2 + 3 + const HeaderSection: FC = () => ( 4 + <section className="header text-2xl font-semibold text-center mb-2 text-text select-none"> 5 + <img 6 + src={browser.runtime.getURL('/icons/128.png')} 7 + alt="Tailscale Logo" 8 + className="inline-block w-8 h-8 mr-2" 9 + /> 10 + tailname 11 + </section> 12 + ); 13 + 14 + export default HeaderSection;
+21
entrypoints/popup/components/HelperText.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiTable } from 'react-icons/bi'; 3 + 4 + interface HelperTextProps { 5 + onShowWordList: () => void; 6 + } 7 + 8 + const HelperText: FC<HelperTextProps> = ({ onShowWordList }) => { 9 + return ( 10 + <button 11 + type="button" 12 + className="w-full text-center border border-accent flex flex-row gap-2 justify-center text-text bg-accent text-sm rounded-md mt-3 py-3 items-center font-medium hover:opacity-80 transition-opacity" 13 + onClick={onShowWordList} 14 + > 15 + <BiTable className="inline-block" size={20} aria-hidden={true} /> 16 + View all available tailnet words 17 + </button> 18 + ); 19 + }; 20 + 21 + export default HelperText;
+49
entrypoints/popup/components/LoadingOverlay.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiLoaderAlt } from 'react-icons/bi'; 3 + 4 + interface LoadingOverlayProps { 5 + loading: boolean; 6 + eligibilityLoading: boolean; 7 + } 8 + 9 + const LoadingOverlay: FC<LoadingOverlayProps> = ({ 10 + loading, 11 + eligibilityLoading, 12 + }) => { 13 + if (!(eligibilityLoading || loading)) return null; 14 + 15 + // Determine which loading state to show 16 + const isEligibility = eligibilityLoading && !loading; 17 + const isRegular = loading && !eligibilityLoading; 18 + const isBoth = eligibilityLoading && loading; 19 + 20 + let loadingText = 'Loading...'; 21 + if (isEligibility && !isBoth) { 22 + loadingText = 'Checking eligibility...'; 23 + } 24 + 25 + if (isRegular && !isBoth) { 26 + loadingText = 'Processing...'; 27 + } 28 + 29 + if (isBoth) { 30 + loadingText = 'Checking eligibility and processing...'; 31 + } 32 + 33 + return ( 34 + <div className="fixed inset-0 flex items-center justify-center bg-background bg-opacity-80 z-50 backdrop-blur-sm"> 35 + <div className="text-text rounded-2xl shadow-lg p-6 bg-header max-w-lg mx-auto flex flex-col items-center"> 36 + <BiLoaderAlt 37 + className="loading-icon text-4xl mb-4 animate-spin-fast" 38 + aria-hidden={true} 39 + /> 40 + 41 + <span className="loading-text text-blue-600 text-lg font-medium"> 42 + {loadingText} 43 + </span> 44 + </div> 45 + </div> 46 + ); 47 + }; 48 + 49 + export default LoadingOverlay;
+441
entrypoints/popup/components/SettingsModal.tsx
···
··· 1 + import { type FC, useEffect, useId, useRef, useState } from 'react'; 2 + import AlertModal from './AlertModal'; 3 + 4 + interface SettingsModalProps { 5 + visible: boolean; 6 + shouldRender: boolean; 7 + interval: number; 8 + setInterval: (val: number) => void; 9 + onClose: () => void; 10 + } 11 + 12 + const SettingsModal: FC<SettingsModalProps> = ({ 13 + visible, 14 + shouldRender, 15 + interval, 16 + setInterval, 17 + onClose, 18 + }) => { 19 + const [inputValue, setInputValue] = useState(String(interval)); 20 + const [isClosing, setIsClosing] = useState(false); 21 + const [ntfyUrl, setNtfyUrl] = useState(''); 22 + const [ntfyTopic, setNtfyTopic] = useState(''); 23 + const [ntfyToken, setNtfyToken] = useState(''); 24 + const [activeTab, setActiveTab] = useState<'general' | 'notifications'>( 25 + 'general', 26 + ); 27 + const [useSystemPush, setUseSystemPush] = useState(true); 28 + const [useNtfyPush, setUseNtfyPush] = useState(false); 29 + const [showAlert, setShowAlert] = useState(false); 30 + const [alertMessage, setAlertMessage] = useState(''); 31 + const [ntfyLoading, setNtfyLoading] = useState(false); 32 + const [tabTransitioning, setTabTransitioning] = useState(false); 33 + 34 + const alertModalRef = useRef<HTMLDivElement | null>(null); 35 + const alertCloseBtnRef = useRef<HTMLButtonElement | null>(null); 36 + 37 + const ntfyUrlId = useId(); 38 + const ntfyTopicId = useId(); 39 + const ntfyTokenId = useId(); 40 + const systemPushId = useId(); 41 + const ntfyPushId = useId(); 42 + 43 + useEffect(() => { 44 + setInputValue(String(interval)); 45 + }, [interval]); 46 + 47 + useEffect(() => { 48 + if (isClosing) { 49 + const timer = setTimeout(() => { 50 + setIsClosing(false); 51 + onClose(); 52 + }, 300); 53 + return () => clearTimeout(timer); 54 + } 55 + }, [isClosing, onClose]); 56 + 57 + useEffect(() => { 58 + // Load notification preferences from storage 59 + browser.storage.local.get( 60 + ['ntfyUrl', 'ntfyTopic', 'ntfyToken', 'useSystemPush', 'useNtfyPush'], 61 + (result) => { 62 + if (result.ntfyUrl) setNtfyUrl(result.ntfyUrl); 63 + if (result.ntfyTopic) setNtfyTopic(result.ntfyTopic); 64 + if (result.ntfyToken) setNtfyToken(result.ntfyToken); 65 + if (typeof result.useSystemPush === 'boolean') 66 + setUseSystemPush(result.useSystemPush); 67 + if (typeof result.useNtfyPush === 'boolean') 68 + setUseNtfyPush(result.useNtfyPush); 69 + }, 70 + ); 71 + }, []); 72 + 73 + const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { 74 + setInputValue(e.target.value); 75 + }; 76 + 77 + const handleClose = () => { 78 + let val = Number(inputValue); 79 + if (Number.isNaN(val) || inputValue.trim() === '') val = interval; 80 + if (val < 5) val = 5; 81 + setInputValue(String(val)); 82 + if (val !== interval) { 83 + setInterval(val); 84 + } 85 + setIsClosing(true); 86 + }; 87 + 88 + const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { 89 + if (e.target === e.currentTarget) { 90 + setIsClosing(true); 91 + } 92 + }; 93 + 94 + const handleBackdropKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { 95 + if (e.key === 'Escape') { 96 + setIsClosing(true); 97 + } 98 + }; 99 + 100 + const handleAlertCloseModal = () => { 101 + setShowAlert(false); 102 + }; 103 + 104 + const handleNtfyTest = async () => { 105 + if (!ntfyUrl || !ntfyTopic) { 106 + setAlertMessage('Please enter both server URL and topic.'); 107 + setShowAlert(true); 108 + return; 109 + } 110 + 111 + setNtfyLoading(true); 112 + 113 + try { 114 + const url = `${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`; 115 + const headers: Record<string, string> = { 'Content-Type': 'text/plain' }; 116 + if (ntfyToken) headers.Authorization = `Bearer ${ntfyToken}`; 117 + 118 + const res = await fetch(url, { 119 + method: 'POST', 120 + headers, 121 + body: 'Test notification from Tailscale WXT', 122 + }); 123 + 124 + if (res.ok) { 125 + setAlertMessage('Test notification sent!'); 126 + setShowAlert(true); 127 + } else if (res.status === 403) { 128 + setAlertMessage( 129 + 'Server returned a 403 error. This likely means an auth token is required.', 130 + ); 131 + setShowAlert(true); 132 + } else { 133 + setAlertMessage(`Failed: ${res.status}`); 134 + setShowAlert(true); 135 + } 136 + } catch (err) { 137 + if (err instanceof Error && err.message === 'Failed to fetch') { 138 + setAlertMessage( 139 + 'Failed to reach the server. Perhaps the URL is incorrect?', 140 + ); 141 + } else { 142 + setAlertMessage( 143 + `Error: ${err instanceof Error ? err.message : String(err)}`, 144 + ); 145 + } 146 + setShowAlert(true); 147 + } 148 + setNtfyLoading(false); 149 + }; 150 + 151 + const handleStorageChange = 152 + <T extends string>(setter: (v: T) => void, key: string) => 153 + (e: React.ChangeEvent<HTMLInputElement>) => { 154 + const value = e.target.value as T; 155 + setter(value); 156 + browser.storage.local.set({ [key]: value }); 157 + }; 158 + const handleCheckboxChange = 159 + (setter: (v: boolean) => void, key: string) => 160 + (e: React.ChangeEvent<HTMLInputElement>) => { 161 + const checked = e.target.checked; 162 + setter(checked); 163 + browser.storage.local.set({ [key]: checked }); 164 + }; 165 + 166 + const handleSystemPushChange = handleCheckboxChange( 167 + setUseSystemPush, 168 + 'useSystemPush', 169 + ); 170 + const handleNtfyPushChange = handleCheckboxChange( 171 + setUseNtfyPush, 172 + 'useNtfyPush', 173 + ); 174 + const handleNtfyUrlChange = handleStorageChange(setNtfyUrl, 'ntfyUrl'); 175 + const handleNtfyTopicChange = handleStorageChange(setNtfyTopic, 'ntfyTopic'); 176 + const handleNtfyTokenChange = handleStorageChange(setNtfyToken, 'ntfyToken'); 177 + 178 + const handleTabSwitch = (tab: 'general' | 'notifications') => { 179 + if (activeTab !== tab) { 180 + setTabTransitioning(true); 181 + setTimeout(() => { 182 + setTabTransitioning(false); 183 + setActiveTab(tab); 184 + }, 250); 185 + } 186 + }; 187 + 188 + if (!shouldRender && !isClosing) return null; 189 + return ( 190 + <> 191 + <div 192 + className={`fixed inset-0 flex items-center justify-center bg-background bg-opacity-40 backdrop-blur-sm z-50 transition-opacity duration-300 ${ 193 + visible && !isClosing ? 'opacity-100' : 'opacity-0' 194 + }`} 195 + aria-modal="true" 196 + role="dialog" 197 + onClick={handleBackdropClick} 198 + onKeyDown={handleBackdropKeyDown} 199 + > 200 + <div 201 + className={`text-text shadow-[#000] rounded-md shadow-2xl p-6 bg-header max-w-md mx-auto flex flex-col border border-border transform transition-all duration-300 ${ 202 + visible && !isClosing 203 + ? 'scale-100 opacity-100' 204 + : 'scale-95 opacity-0' 205 + }`} 206 + > 207 + <h2 className="text-2xl font-bold mb-2">Settings</h2> 208 + <div className="flex flex-row gap-2 mb-4"> 209 + <button 210 + className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors duration-150 hover:outline-double hover:outline-2 ${ 211 + activeTab === 'general' 212 + ? 'bg-accent text-text font-bold shadow' 213 + : 'bg-item text-text hover:bg-accent/10 hover:text-accent' 214 + }`} 215 + onClick={() => handleTabSwitch('general')} 216 + type="button" 217 + aria-selected={activeTab === 'general'} 218 + aria-controls="general-tab" 219 + role="tab" 220 + > 221 + General 222 + </button> 223 + <button 224 + className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors duration-150 hover:outline-double hover:outline-2 ${ 225 + activeTab === 'notifications' 226 + ? 'bg-accent text-text font-bold shadow' 227 + : 'bg-item text-text hover:bg-accent/10 hover:text-accent' 228 + }`} 229 + onClick={() => handleTabSwitch('notifications')} 230 + type="button" 231 + aria-selected={activeTab === 'notifications'} 232 + aria-controls="notifications-tab" 233 + role="tab" 234 + > 235 + Notifications 236 + </button> 237 + </div> 238 + 239 + <div className="min-w-[380px]"> 240 + <div 241 + className={`transition-all duration-300 ${ 242 + activeTab === 'general' 243 + ? tabTransitioning 244 + ? 'opacity-0 translate-x-4 pointer-events-none' 245 + : 'opacity-100 translate-x-0' 246 + : 'opacity-0 pointer-events-none' 247 + } bg-header`} 248 + aria-labelledby="general-tab" 249 + role="tabpanel" 250 + > 251 + {activeTab === 'general' && ( 252 + <div className="flex flex-col gap-2"> 253 + <label className="text-base font-medium flex flex-col"> 254 + <span className="text-sm font-medium mb-1"> 255 + Check interval (seconds): 256 + </span> 257 + <input 258 + type="number" 259 + min={0} 260 + value={inputValue} 261 + onChange={handleInputChange} 262 + className="px-2 py-1 border rounded-md bg-item text-text w-full" 263 + /> 264 + </label> 265 + 266 + <div className="gap-0.5 flex flex-col"> 267 + <p className="text-sm text-subtext"> 268 + How often to check Tailscale for new tailnet name offers. 269 + </p> 270 + <p className="text-sm text-subtext"> 271 + The minimum value is 5 seconds. 272 + </p> 273 + </div> 274 + </div> 275 + )} 276 + </div> 277 + 278 + <div 279 + className={`transition-all duration-300 ${ 280 + activeTab === 'notifications' 281 + ? tabTransitioning 282 + ? 'opacity-0 -translate-x-4 pointer-events-none' 283 + : 'opacity-100 translate-x-0' 284 + : 'opacity-0 pointer-events-none' 285 + } bg-header`} 286 + aria-labelledby="notifications-tab" 287 + role="tabpanel" 288 + > 289 + {activeTab === 'notifications' && ( 290 + <div className="flex flex-col gap-3"> 291 + <div className="flex items-center gap-2"> 292 + <input 293 + id={systemPushId} 294 + type="checkbox" 295 + checked={useSystemPush} 296 + onChange={handleSystemPushChange} 297 + className="accent-accent w-4 h-4 cursor-pointer" 298 + /> 299 + <label 300 + htmlFor={systemPushId} 301 + className="text-sm text-text cursor-pointer" 302 + > 303 + Use system push notifications 304 + </label> 305 + </div> 306 + 307 + <div className="flex items-center gap-2"> 308 + <input 309 + id={ntfyPushId} 310 + type="checkbox" 311 + checked={useNtfyPush} 312 + onChange={handleNtfyPushChange} 313 + className="accent-accent w-4 h-4 cursor-pointer" 314 + /> 315 + <label 316 + htmlFor={ntfyPushId} 317 + className="text-sm text-text cursor-pointer" 318 + > 319 + Use ntfy push notifications 320 + </label> 321 + </div> 322 + 323 + <p className="text-xs text-subtext"> 324 + You will {!useSystemPush && !useNtfyPush ? 'not ' : ''} 325 + receive notifications{' '} 326 + {useSystemPush && useNtfyPush 327 + ? 'via system & ntfy' 328 + : useSystemPush 329 + ? 'via system' 330 + : useNtfyPush 331 + ? 'via ntfy' 332 + : ''}{' '} 333 + for new tailnet offers. 334 + </p> 335 + 336 + <div className="flex flex-col gap-2"> 337 + <label 338 + htmlFor={ntfyUrlId} 339 + className={`text-sm font-medium ${ 340 + !useNtfyPush ? 'text-subtext' : '' 341 + }`} 342 + > 343 + ntfy server URL 344 + </label> 345 + <input 346 + id={ntfyUrlId} 347 + type="text" 348 + value={ntfyUrl} 349 + onChange={handleNtfyUrlChange} 350 + disabled={!useNtfyPush} 351 + placeholder="https://ntfy.sh" 352 + className={`px-2 py-1 border rounded-md bg-item text-text w-full transition-opacity ${ 353 + !useNtfyPush ? 'opacity-50 cursor-not-allowed' : '' 354 + }`} 355 + /> 356 + <label 357 + htmlFor={ntfyTopicId} 358 + className={`text-sm font-medium ${ 359 + !useNtfyPush ? 'text-subtext' : '' 360 + }`} 361 + > 362 + ntfy topic 363 + </label> 364 + <input 365 + id={ntfyTopicId} 366 + type="text" 367 + value={ntfyTopic} 368 + onChange={handleNtfyTopicChange} 369 + disabled={!useNtfyPush} 370 + placeholder="tailscale" 371 + className={`px-2 py-1 border rounded-md bg-item text-text w-full transition-opacity ${ 372 + !useNtfyPush ? 'opacity-50 cursor-not-allowed' : '' 373 + }`} 374 + /> 375 + <label 376 + htmlFor={ntfyTokenId} 377 + className={`text-sm font-medium ${ 378 + !useNtfyPush ? 'text-subtext' : '' 379 + }`} 380 + > 381 + ntfy auth token (optional) 382 + </label> 383 + <input 384 + id={ntfyTokenId} 385 + type="password" 386 + value={ntfyToken} 387 + onChange={handleNtfyTokenChange} 388 + disabled={!useNtfyPush} 389 + placeholder="tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 390 + className={`px-2 py-1 border rounded-md bg-item text-text w-full transition-opacity ${ 391 + !useNtfyPush ? 'opacity-50 cursor-not-allowed' : '' 392 + } [&::-ms-reveal]:invert`} 393 + /> 394 + <button 395 + type="button" 396 + disabled={ 397 + !useNtfyPush || !(ntfyUrl && ntfyTopic) || ntfyLoading 398 + } 399 + className={`px-3 py-2 rounded-lg text-xs bg-accent text-text font-medium mt-2 transition-opacity flex items-center justify-center gap-2 ${ 400 + !useNtfyPush || !(ntfyUrl && ntfyTopic) || ntfyLoading 401 + ? 'opacity-50 cursor-not-allowed' 402 + : 'hover:opacity-80' 403 + }`} 404 + onClick={handleNtfyTest} 405 + > 406 + {ntfyLoading ? ( 407 + <div className="w-4 h-4 text-text border-2 border-gray-300 border-t-accent rounded-full animate-spin"></div> 408 + ) : ( 409 + 'Send test notification' 410 + )} 411 + </button> 412 + </div> 413 + </div> 414 + )} 415 + </div> 416 + </div> 417 + 418 + <div className="flex gap-2 mt-4"> 419 + <button 420 + className="px-4 py-2 rounded-lg bg-accent text-text font-medium hover:opacity-80 transition-opacity" 421 + type="button" 422 + onClick={handleClose} 423 + > 424 + Close 425 + </button> 426 + </div> 427 + </div> 428 + </div> 429 + 430 + <AlertModal 431 + showAlert={showAlert} 432 + alertMessage={alertMessage} 433 + alertModalRef={alertModalRef} 434 + alertCloseBtnRef={alertCloseBtnRef} 435 + handleAlertCloseModal={handleAlertCloseModal} 436 + /> 437 + </> 438 + ); 439 + }; 440 + 441 + export default SettingsModal;
+181
entrypoints/popup/components/StatusControls.tsx
···
··· 1 + import { type FC, useEffect, useState } from 'react'; 2 + import { BiLoaderAlt, BiPlay, BiSolidCog, BiStop } from 'react-icons/bi'; 3 + import { MdOutlineWarning } from 'react-icons/md'; 4 + import SettingsModal from '$components/SettingsModal'; 5 + import { useNotificationSettings } from '$hooks/useNotificationSettings'; 6 + import { useSettings } from '$hooks/useSettings'; 7 + 8 + interface StatusControlsProps { 9 + status: string; 10 + timer: number | null; 11 + handleStart: () => void; 12 + handleStop: () => void; 13 + tailnetNames: string[]; 14 + redirectToTailnetList: () => void; 15 + } 16 + 17 + const StatusControls: FC<StatusControlsProps> = ({ 18 + status, 19 + timer, 20 + handleStart, 21 + handleStop, 22 + tailnetNames, 23 + redirectToTailnetList, 24 + }) => { 25 + const settings = useSettings(30); 26 + const [showMessage, setShowMessage] = useState(false); 27 + const [shouldRender, setShouldRender] = useState(false); 28 + const [hasBeenToggled, setHasBeenToggled] = useState(false); 29 + const [pendingAction, setPendingAction] = useState<'start' | null>(null); 30 + const notificationsEnabled = useNotificationSettings(); 31 + 32 + // Only show loading if pendingAction is 'start' and status is not yet updated 33 + const isLoading = pendingAction === 'start' && status === 'Stopped'; 34 + 35 + useEffect(() => { 36 + let timer: NodeJS.Timeout; 37 + 38 + if (status !== 'Stopped') { 39 + setShouldRender(true); 40 + timer = setTimeout( 41 + () => { 42 + setShowMessage(true); 43 + }, 44 + hasBeenToggled ? 50 : 0, 45 + ); 46 + // Clear pendingAction when UI is ready (status is running and message is shown) 47 + if (pendingAction === 'start' && showMessage) setPendingAction(null); 48 + } else { 49 + setShowMessage(false); 50 + timer = setTimeout( 51 + () => { 52 + setShouldRender(false); 53 + }, 54 + hasBeenToggled ? 200 : 0, 55 + ); 56 + // No pendingAction logic for stop 57 + } 58 + 59 + return () => clearTimeout(timer); 60 + }, [status, hasBeenToggled, pendingAction, showMessage]); 61 + 62 + const handleStartClick = async () => { 63 + setHasBeenToggled(true); 64 + setPendingAction('start'); 65 + await Promise.resolve(handleStart()); 66 + }; 67 + 68 + const handleStopClick = async () => { 69 + await Promise.resolve(handleStop()); 70 + }; 71 + 72 + return ( 73 + <> 74 + <div className="flex gap-3 pb-4 justify-center items-center"> 75 + {status === 'Stopped' ? ( 76 + <button 77 + className="px-4 py-2 rounded-lg bg-accent flex flex-row items-center gap-1 text-text text-sm font-medium disabled:opacity-50 hover:opacity-80 transition-opacity disabled:cursor-not-allowed" 78 + type="button" 79 + onClick={handleStartClick} 80 + disabled={tailnetNames.length === 0 || isLoading} 81 + > 82 + {isLoading ? ( 83 + <BiLoaderAlt 84 + className="inline-block animate-spin" 85 + size={20} 86 + aria-hidden={true} 87 + /> 88 + ) : ( 89 + <BiPlay className="inline-block" size={20} aria-hidden={true} /> 90 + )} 91 + Start 92 + </button> 93 + ) : ( 94 + <button 95 + className="px-4 py-2 rounded-lg bg-danger flex flex-row items-center gap-1 text-text text-sm font-medium disabled:opacity-50 hover:opacity-80 transition-opacity disabled:cursor-not-allowed" 96 + type="button" 97 + onClick={handleStopClick} 98 + > 99 + <BiStop className="inline-block" size={20} aria-hidden={true} /> 100 + Stop 101 + </button> 102 + )} 103 + <button 104 + className="px-3 py-2 rounded-lg bg-item flex flex-row items-center gap-2 border border-border text-text text-sm font-medium hover:opacity-80 transition-opacity ml-2" 105 + type="button" 106 + aria-label="Settings" 107 + onClick={settings.open} 108 + > 109 + <BiSolidCog className="inline-block" size={20} aria-hidden={true} /> 110 + Settings 111 + </button> 112 + </div> 113 +       114 + <SettingsModal 115 + visible={settings.visible} 116 + shouldRender={settings.shouldRender} 117 + interval={settings.interval} 118 + setInterval={settings.setInterval} 119 + onClose={settings.close} 120 + /> 121 +       122 + {tailnetNames.length === 0 && ( 123 + <div className="flex"> 124 + <button 125 + className="w-full text-sm text-center border border-border text-text bg-item mx-4 rounded-md mb-4 py-4 items-center cursor-pointer hover:scale-105 transition-transform" 126 + onClick={redirectToTailnetList} 127 + aria-label="Go to Tailnet List" 128 + type="button" 129 + > 130 + <p> 131 + <MdOutlineWarning 132 + className="inline-block text-warning mr-1" 133 + size={20} 134 + aria-hidden={true} 135 + /> 136 + You'll need to add at least one tailnet word in order to start 137 + searching. 138 + </p> 139 + 140 + <p className="font-medium">Click here to start adding words.</p> 141 + </button> 142 + </div> 143 + )} 144 +       145 + {tailnetNames.length !== 0 && shouldRender && ( 146 + <div 147 + className={`flex flex-col pb-4 ${ 148 + hasBeenToggled ? 'transition-opacity duration-500' : '' 149 + } ${showMessage ? 'opacity-100' : 'opacity-0'}`} 150 + > 151 + <div className="flex flex-col gap-0.5 border text-sm border-border text-text bg-item mx-4 rounded-md p-4"> 152 + <p> 153 + You can now leave this extension running in the background.{' '} 154 + {notificationsEnabled ? ( 155 + "You'll receive a notification when a tailnet offer is found that matches one of your words." 156 + ) : ( 157 + <span className="text-warning font-semibold"> 158 + Notifications are currently disabled. You won't receive alerts 159 + for new tailnet offers. 160 + </span> 161 + )} 162 + </p> 163 + 164 + <p className="text-subtext text-xs"> 165 + Next check in{' '} 166 + {isLoading 167 + ? `${settings.interval} second${settings.interval === 1 ? '' : 's'}` 168 + : timer !== null 169 + ? `${timer} second${timer === 1 ? '' : 's'}` 170 + : `a few second${timer === 1 ? '' : 's'}`} 171 + . 172 + </p> 173 + </div> 174 + </div> 175 + )} 176 +     177 + </> 178 + ); 179 + }; 180 + 181 + export default StatusControls;
+127
entrypoints/popup/components/TailnetInput.tsx
···
··· 1 + import { type FC, useId } from 'react'; 2 + import { BiPlus } from 'react-icons/bi'; 3 + 4 + interface TailnetInputProps { 5 + inputValue: string; 6 + setInputValue: (val: string) => void; 7 + isInputValid: string | boolean; 8 + handleAddTailnet: () => void; 9 + tails: string[]; 10 + scales: string[]; 11 + tailnetNames: string[]; 12 + loading: boolean; 13 + } 14 + 15 + const TailnetInput: FC<TailnetInputProps> = ({ 16 + inputValue, 17 + setInputValue, 18 + isInputValid, 19 + handleAddTailnet, 20 + tails, 21 + scales, 22 + tailnetNames, 23 + loading, 24 + }) => { 25 + const datalistId = useId(); 26 + 27 + const words = [...tails, ...scales]; 28 + 29 + let comboSuggestions: string[] = []; 30 + if (inputValue.includes('-')) { 31 + const [tailPrefix, scalePrefix] = inputValue.split('-'); 32 + comboSuggestions = tails 33 + .filter((tail) => tail.startsWith(tailPrefix)) 34 + .flatMap((tail) => 35 + scales 36 + .filter((scale) => scale.startsWith(scalePrefix || '')) 37 + .map((scale) => `${tail}-${scale}`), 38 + ) 39 + .filter((combo) => !tailnetNames.includes(combo)); 40 + } 41 + 42 + const isWordUsed = (word: string) => 43 + tailnetNames.some( 44 + (name) => 45 + name === word || 46 + name.split('-')[0] === word || 47 + name.split('-')[1] === word, 48 + ); 49 + 50 + const singleWordSuggestions = 51 + !inputValue.includes('-') && inputValue.length > 0 52 + ? Array.from( 53 + new Set( 54 + words.filter( 55 + (word) => !isWordUsed(word) && word.startsWith(inputValue), 56 + ), 57 + ), 58 + ) 59 + : []; 60 + 61 + return ( 62 + <div className="flex flex-col mb-2"> 63 + <div className="flex flex-row w-full gap-2"> 64 + <input 65 + type="text" 66 + className="w-full border border-border rounded-lg px-3 py-1.5 bg-input text-base text-text focus:border-accent focus:outline-none disabled:cursor-not-allowed" 67 + placeholder="Enter a tailnet word (bunny) or combo (miku-kitchen)..." 68 + value={inputValue} 69 + onChange={(e) => setInputValue(e.target.value)} 70 + onKeyDown={(e) => { 71 + if (e.key === 'Enter' && !!isInputValid) { 72 + handleAddTailnet(); 73 + } 74 + }} 75 + list={datalistId} 76 + disabled={loading} 77 + /> 78 + 79 + <button 80 + className="flex flex-row items-center px-3 gap-1 rounded-lg bg-accent text-text font-medium disabled:opacity-50 hover:opacity-80 disabled:cursor-not-allowed" 81 + type="button" 82 + onClick={handleAddTailnet} 83 + disabled={!isInputValid || isInputValid === ''} 84 + > 85 + <BiPlus className="inline-block" size={20} aria-hidden={true} /> 86 + Add 87 + </button> 88 + </div> 89 + 90 + <datalist id={datalistId}> 91 + {singleWordSuggestions.map((word: string) => ( 92 + <option key={word} value={word} /> 93 + ))} 94 + 95 + {comboSuggestions.map((combo) => ( 96 + <option key={combo} value={combo} /> 97 + ))} 98 + </datalist> 99 + 100 + {inputValue.includes('-') && comboSuggestions.length === 0 && ( 101 + <div className="w-full text-center border border-border text-text bg-item rounded-md mt-3 py-4 items-center font-medium"> 102 + <p>⚠️ No valid combos found for your input.</p> 103 + 104 + <p> 105 + The combo must match the format of &lt;tail&gt;-&lt;scale&gt; (e.g. 106 + miku-kitchen). 107 + </p> 108 + </div> 109 + )} 110 + 111 + {!inputValue.includes('-') && 112 + inputValue.length > 0 && 113 + singleWordSuggestions.length === 0 && ( 114 + <div className="w-full text-center border border-border text-text bg-item rounded-md mt-3 py-4 items-center font-medium "> 115 + <p>⚠️ No valid single words were found for your input.</p> 116 + 117 + <p> 118 + Either the matching words have already been added or they do not 119 + exist. 120 + </p> 121 + </div> 122 + )} 123 + </div> 124 + ); 125 + }; 126 + 127 + export default TailnetInput;
+38
entrypoints/popup/components/TailnetList.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiX } from 'react-icons/bi'; 3 + 4 + interface TailnetListProps { 5 + tailnetNames: string[]; 6 + handleRemoveTailnet: (name: string) => void; 7 + } 8 + 9 + const TailnetList: FC<TailnetListProps> = ({ 10 + tailnetNames, 11 + handleRemoveTailnet, 12 + }) => { 13 + if (tailnetNames.length === 0) return null; 14 + return ( 15 + <div className="bg-header border border-border rounded-lg p-4 mt-4 max-h-[23em] overflow-y-auto [&::-webkit-scrollbar]:[width:0.5em] [&::-webkit-scrollbar-thumb]:bg-subtext"> 16 + <div className="flex flex-wrap gap-2"> 17 + {tailnetNames.map((name: string) => ( 18 + <span 19 + key={name} 20 + className="flex items-center gap-2.5 bg-item text-text rounded-md px-3 py-1 text-base font-medium shadow-sm border border-border" 21 + > 22 + {name} 23 + <button 24 + className="text-lg text-subtext transition-colors hover:rounded-lg hover:bg-danger hover:text-text rounded-full flex" 25 + type="button" 26 + onClick={() => handleRemoveTailnet(name)} 27 + aria-label={`Remove ${name}`} 28 + > 29 + <BiX size={20} /> 30 + </button> 31 + </span> 32 + ))} 33 + </div> 34 + </div> 35 + ); 36 + }; 37 + 38 + export default TailnetList;
+115
entrypoints/popup/components/TokenList.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { MdCheck, MdDelete, MdOutlineWarning } from 'react-icons/md'; 3 + import { extractTailnetNameFromToken } from '$helpers/tokens'; 4 + import type { Token } from '$types/tokens'; 5 + 6 + interface TokenListProps { 7 + tokens: Token[]; 8 + handleClaimToken: (tokenObj: Token) => Promise<void>; 9 + handleRemoveToken: (tokenObj: Token) => void; 10 + loading: boolean; 11 + error: string | null; 12 + } 13 + 14 + const TokenList: FC<TokenListProps> = ({ 15 + tokens, 16 + handleClaimToken, 17 + handleRemoveToken, 18 + loading, 19 + error, 20 + }) => { 21 + return ( 22 + <div> 23 + <div className="bg-item flex flex-row items-center gap-1 text-sm border-border border px-3 py-3 rounded-lg text-text mb-3"> 24 + <MdOutlineWarning 25 + className="text-warning" 26 + size={20} 27 + aria-hidden={true} 28 + /> 29 + Each tailnet offer only last 5 minutes. Make sure to use them quickly! 30 + </div> 31 + 32 + <div className="bg-header border border-border text-text rounded-lg p-4 max-h-[25rem] overflow-y-auto [&::-webkit-scrollbar]:[width:0.5em] [&::-webkit-scrollbar-thumb]:bg-subtext"> 33 + <ul className="flex flex-col gap-4"> 34 + {tokens.length > 0 ? ( 35 + tokens.map((tokenObj, idx) => { 36 + const expiresAt = tokenObj.timestamp 37 + ? tokenObj.timestamp + 5 * 60 * 1000 38 + : null; 39 + const timeLeft = expiresAt ? expiresAt - Date.now() : null; 40 + let expireColor = 'bg-header border-border'; 41 + if (timeLeft !== null) { 42 + if (timeLeft < 60 * 1000) 43 + expireColor = 'bg-danger border-danger'; 44 + else if (timeLeft < 2 * 60 * 1000) 45 + expireColor = 'bg-warning text-header border-warning'; 46 + } 47 + return ( 48 + <li 49 + key={`${tokenObj.token}-${idx}`} 50 + className="flex flex-row items-center justify-between gap-4 px-3 py-2.5 rounded-lg border border-border bg-item" 51 + > 52 + <div className="flex flex-col gap-1"> 53 + <span className="font-semibold text-base text-text truncate"> 54 + {extractTailnetNameFromToken(tokenObj.token)} 55 + </span> 56 + 57 + {expiresAt && 58 + (Date.now() > expiresAt ? ( 59 + <span className="px-2 py-1 rounded-lg text-xs font-medium bg-danger text-text border border-danger w-fit"> 60 + Token expired 61 + </span> 62 + ) : ( 63 + <span 64 + className={`px-2 py-1 rounded-lg text-xs font-medium ${expireColor} border w-fit`} 65 + > 66 + Expires at{' '} 67 + {new Date(expiresAt).toLocaleTimeString([], { 68 + hour: '2-digit', 69 + minute: '2-digit', 70 + second: '2-digit', 71 + })} 72 + </span> 73 + ))} 74 + </div> 75 + <div className="flex flex-row gap-2 items-center"> 76 + <button 77 + className={`px-3 py-2 rounded-lg bg-accent text-text text-sm font-semibold transition flex items-center gap-1 ${loading || Boolean(expiresAt && Date.now() > expiresAt) ? 'opacity-50 cursor-not-allowed' : 'hover:opacity-80 transition-opacity'}`} 78 + type="button" 79 + onClick={() => handleClaimToken(tokenObj)} 80 + disabled={ 81 + loading || Boolean(expiresAt && Date.now() > expiresAt) 82 + } 83 + aria-label="Claim token" 84 + > 85 + <MdCheck size={20} /> 86 + <span>Claim</span> 87 + </button> 88 + <button 89 + className="px-3 py-2 rounded-lg bg-danger text-text text-sm font-semibold hover:opacity-80 transition-opacity flex items-center gap-1" 90 + type="button" 91 + onClick={() => handleRemoveToken(tokenObj)} 92 + disabled={loading} 93 + aria-label="Remove token" 94 + > 95 + <MdDelete size={20} /> 96 + <span>Remove</span> 97 + </button> 98 + </div> 99 + </li> 100 + ); 101 + }) 102 + ) : ( 103 + <li className="text-subtext text-center py-4"> 104 + No matching offers found yet. 105 + </li> 106 + )} 107 + </ul> 108 + </div> 109 + 110 + {error && <div className="text-danger mt-2">{error}</div>} 111 + </div> 112 + ); 113 + }; 114 + 115 + export default TokenList;
+37
entrypoints/popup/hooks/useEligibility.ts
···
··· 1 + import { useEffect, useState } from 'react'; 2 + import { checkEligibility } from '$helpers/eligibility'; 3 + import type { Message } from '$types/messages'; 4 + 5 + export const useEligibility = () => { 6 + const [eligibility, setEligibility] = useState<{ 7 + eligible: boolean; 8 + reason: string; 9 + id?: string; 10 + } | null>(null); 11 + const [eligibilityLoading, setEligibilityLoading] = useState(true); 12 + const [loading, setLoading] = useState(true); 13 + 14 + useEffect(() => { 15 + setEligibilityLoading(true); 16 + checkEligibility().then((result) => { 17 + setEligibility(result); 18 + setEligibilityLoading(false); 19 + setLoading(false); 20 + }); 21 + 22 + const listener = (message: Message) => { 23 + if (message.action === 'refreshEligibility') { 24 + setEligibilityLoading(true); 25 + checkEligibility().then((result) => { 26 + setEligibility(result); 27 + setEligibilityLoading(false); 28 + setLoading(false); 29 + }); 30 + } 31 + }; 32 + browser.runtime.onMessage.addListener(listener); 33 + return () => browser.runtime.onMessage.removeListener(listener); 34 + }, []); 35 + 36 + return { eligibility, eligibilityLoading, loading, setLoading }; 37 + };
+25
entrypoints/popup/hooks/useInputValidation.ts
···
··· 1 + import { useMemo } from 'react'; 2 + 3 + const isCombo = (val: string, tails: string[], scales: string[]) => { 4 + const parts = val.split('-'); 5 + return ( 6 + parts.length === 2 && tails.includes(parts[0]) && scales.includes(parts[1]) 7 + ); 8 + }; 9 + 10 + export const useInputValidation = ( 11 + inputValue: string, 12 + tails: string[], 13 + scales: string[], 14 + tailnetNames: string[], 15 + ) => { 16 + return useMemo( 17 + () => 18 + inputValue && 19 + (tails.includes(inputValue) || 20 + scales.includes(inputValue) || 21 + isCombo(inputValue, tails, scales)) && 22 + !tailnetNames.includes(inputValue), 23 + [inputValue, tails, scales, tailnetNames], 24 + ); 25 + };
+86
entrypoints/popup/hooks/useModal.ts
···
··· 1 + import { useCallback, useEffect, useRef } from 'react'; 2 + import type { RuntimeMessage } from '$types/messages'; 3 + import type { Token } from '$types/tokens'; 4 + 5 + export const useModal = ( 6 + showAlert: boolean, 7 + setShowAlert: (v: boolean) => void, 8 + setAlertMessage: (v: string) => void, 9 + claimedToken: string | null, 10 + setClaimedToken: (v: string | null) => void, 11 + setTokens: (fn: (prevTokens: Token[]) => Token[]) => void, 12 + setTailnetNames: (v: string[]) => void, 13 + ) => { 14 + const alertModalRef = useRef<HTMLDivElement>(null); 15 + const alertCloseBtnRef = useRef<HTMLButtonElement>(null); 16 + 17 + const handleAlertCloseModal = useCallback(() => { 18 + setShowAlert(false); 19 + setTimeout(() => { 20 + setAlertMessage(''); 21 + }, 300); 22 + browser.runtime.sendMessage({ action: 'userAcknowledgedRedirect' }); 23 + if (claimedToken) { 24 + setTokens((prevTokens: Token[]) => 25 + prevTokens.filter((t) => t.token !== claimedToken), 26 + ); 27 + browser.storage.local.get(['tailscaleTokens'], (result) => { 28 + const updatedTokens = (result.tailscaleTokens || []).filter( 29 + (t: Token) => t.token !== claimedToken, 30 + ); 31 + browser.storage.local.set({ tailscaleTokens: updatedTokens }, () => { 32 + browser.runtime.sendMessage({ action: 'updateBadge' }); 33 + }); 34 + }); 35 + browser.storage.local.get(['tailnetNames'], (result) => { 36 + const tailnetName = claimedToken.split('.')[0]; 37 + const updatedNames = (result.tailnetNames || []).filter( 38 + (n: string) => n !== tailnetName, 39 + ); 40 + setTailnetNames(updatedNames); 41 + browser.storage.local.set({ tailnetNames: updatedNames }); 42 + }); 43 + setClaimedToken(null); 44 + } 45 + }, [ 46 + setShowAlert, 47 + setAlertMessage, 48 + claimedToken, 49 + setTokens, 50 + setTailnetNames, 51 + setClaimedToken, 52 + ]); 53 + 54 + useEffect(() => { 55 + if (showAlert && alertModalRef.current && alertCloseBtnRef.current) { 56 + const focusableEls = [alertCloseBtnRef.current]; 57 + const handleKeyDown = (e: KeyboardEvent) => { 58 + if (e.key === 'Tab') { 59 + e.preventDefault(); 60 + focusableEls[0].focus(); 61 + } 62 + if (e.key === 'Escape') { 63 + handleAlertCloseModal(); 64 + } 65 + }; 66 + alertModalRef.current.focus(); 67 + alertModalRef.current.addEventListener('keydown', handleKeyDown); 68 + return () => { 69 + alertModalRef.current?.removeEventListener('keydown', handleKeyDown); 70 + }; 71 + } 72 + }, [showAlert, handleAlertCloseModal]); 73 + 74 + useEffect(() => { 75 + const listener = (message: RuntimeMessage<{ message?: string }>) => { 76 + if (message.action === 'showCustomAlert' && message.message) { 77 + setAlertMessage(message.message); 78 + setShowAlert(true); 79 + } 80 + }; 81 + browser.runtime.onMessage.addListener(listener); 82 + return () => browser.runtime.onMessage.removeListener(listener); 83 + }, [setAlertMessage, setShowAlert]); 84 + 85 + return { alertModalRef, alertCloseBtnRef, handleAlertCloseModal }; 86 + };
+39
entrypoints/popup/hooks/useNotificationSettings.ts
···
··· 1 + import { useCallback, useEffect, useState } from 'react'; 2 + 3 + export const useNotificationSettings = () => { 4 + const [notificationsEnabled, setNotificationsEnabled] = useState< 5 + boolean | null 6 + >(null); 7 + 8 + const fetchNotificationSettings = useCallback(() => { 9 + browser.storage.local.get(['useSystemPush', 'useNtfyPush'], (result) => { 10 + const enabled = 11 + Boolean(result.useSystemPush) || Boolean(result.useNtfyPush); 12 + setNotificationsEnabled(enabled); 13 + }); 14 + }, []); 15 + 16 + useEffect(() => { 17 + fetchNotificationSettings(); 18 + }, [fetchNotificationSettings]); 19 + 20 + useEffect(() => { 21 + const handleStorageChange = ( 22 + changes: Record<string, Browser.storage.StorageChange>, 23 + areaName: string, 24 + ) => { 25 + if ( 26 + areaName === 'local' && 27 + ('useSystemPush' in changes || 'useNtfyPush' in changes) 28 + ) { 29 + fetchNotificationSettings(); 30 + } 31 + }; 32 + browser.storage.onChanged.addListener(handleStorageChange); 33 + return () => { 34 + browser.storage.onChanged.removeListener(handleStorageChange); 35 + }; 36 + }, [fetchNotificationSettings]); 37 + 38 + return notificationsEnabled; 39 + };
+71
entrypoints/popup/hooks/useSettings.ts
···
··· 1 + import { useEffect, useRef, useState } from 'react'; 2 + 3 + export const useSettings = (defaultInterval: number) => { 4 + const [show, setShow] = useState(false); 5 + const [visible, setVisible] = useState(false); 6 + const [shouldRender, setShouldRender] = useState(false); 7 + const [interval, setInterval] = useState(defaultInterval); 8 + const timeoutRef = useRef<NodeJS.Timeout | null>(null); 9 + 10 + useEffect(() => { 11 + browser.storage.local.get(['checkInterval'], (result) => { 12 + if (result.checkInterval) setInterval(result.checkInterval); 13 + }); 14 + }, []); 15 + 16 + useEffect(() => { 17 + if (timeoutRef.current) { 18 + clearTimeout(timeoutRef.current); 19 + timeoutRef.current = null; 20 + } 21 + if (show) { 22 + setShouldRender(true); 23 + timeoutRef.current = setTimeout(() => setVisible(true), 10); 24 + } else if (visible) { 25 + setVisible(false); 26 + timeoutRef.current = setTimeout(() => setShouldRender(false), 300); 27 + } else { 28 + setShouldRender(false); 29 + } 30 + return () => { 31 + if (timeoutRef.current) { 32 + clearTimeout(timeoutRef.current); 33 + timeoutRef.current = null; 34 + } 35 + }; 36 + }, [show, visible]); 37 + 38 + const open = () => { 39 + if (timeoutRef.current) { 40 + clearTimeout(timeoutRef.current); 41 + timeoutRef.current = null; 42 + } 43 + setShow(true); 44 + }; 45 + const close = () => { 46 + if (timeoutRef.current) { 47 + clearTimeout(timeoutRef.current); 48 + timeoutRef.current = null; 49 + } 50 + setShow(false); 51 + }; 52 + const handleIntervalChange = (val: number) => { 53 + const safeVal = Math.max(5, val); 54 + setInterval(safeVal); 55 + browser.storage.local.set({ checkInterval: safeVal }); 56 + browser.runtime.sendMessage({ 57 + action: 'setAlarmInterval', 58 + interval: safeVal, 59 + }); 60 + }; 61 + 62 + return { 63 + show, 64 + visible, 65 + shouldRender, 66 + interval, 67 + setInterval: handleIntervalChange, 68 + open, 69 + close, 70 + }; 71 + };
+19
entrypoints/popup/hooks/useStatus.ts
···
··· 1 + import { useCallback } from 'react'; 2 + 3 + export const useStatus = ( 4 + setStatus: (status: 'Running' | 'Stopped') => void, 5 + ) => { 6 + const handleStart = useCallback(() => { 7 + browser.runtime.sendMessage({ action: 'startCheck' }, (response) => { 8 + setStatus(response?.status || 'Running'); 9 + }); 10 + }, [setStatus]); 11 + 12 + const handleStop = useCallback(() => { 13 + browser.runtime.sendMessage({ action: 'stopCheck' }, (response) => { 14 + setStatus(response?.status || 'Stopped'); 15 + }); 16 + }, [setStatus]); 17 + 18 + return { handleStart, handleStop }; 19 + };
+68
entrypoints/popup/hooks/useTailnetNames.ts
···
··· 1 + import { useCallback, useEffect, useState } from 'react'; 2 + import { getTailnetNames } from '$helpers/tailnet'; 3 + 4 + const isCombo = (val: string, tails: string[], scales: string[]) => { 5 + const parts = val.split('-'); 6 + return ( 7 + parts.length === 2 && tails.includes(parts[0]) && scales.includes(parts[1]) 8 + ); 9 + }; 10 + 11 + export const useTailnetNames = ( 12 + tails: string[], 13 + scales: string[], 14 + setStatus: (status: 'Running' | 'Stopped') => void, 15 + inputValue: string, 16 + setInputValue: (v: string) => void, 17 + ) => { 18 + const [tailnetNames, setTailnetNames] = useState<string[]>([]); 19 + 20 + useEffect(() => { 21 + getTailnetNames().then(setTailnetNames); 22 + }, []); 23 + 24 + const isValid = useCallback( 25 + (val: string) => { 26 + return ( 27 + (val && tails.includes(val)) || 28 + (val && scales.includes(val)) || 29 + isCombo(val, tails, scales) 30 + ); 31 + }, 32 + [tails, scales], 33 + ); 34 + 35 + const handleAddTailnet = useCallback(() => { 36 + if ( 37 + inputValue && 38 + isValid(inputValue) && 39 + !tailnetNames.includes(inputValue) 40 + ) { 41 + const updated = [...tailnetNames, inputValue]; 42 + setTailnetNames(updated); 43 + browser.storage.local.set({ tailnetNames: updated }); 44 + setInputValue(''); 45 + } 46 + }, [inputValue, tailnetNames, setInputValue, isValid]); 47 + 48 + const handleRemoveTailnet = useCallback( 49 + (name: string) => { 50 + const updated = tailnetNames.filter((n) => n !== name); 51 + setTailnetNames(updated); 52 + browser.storage.local.set({ tailnetNames: updated }); 53 + if (updated.length === 0) { 54 + browser.runtime.sendMessage({ action: 'stopCheck' }, (response) => { 55 + setStatus(response?.status || 'Stopped'); 56 + }); 57 + } 58 + }, 59 + [tailnetNames, setStatus], 60 + ); 61 + 62 + return { 63 + tailnetNames, 64 + setTailnetNames, 65 + handleAddTailnet, 66 + handleRemoveTailnet, 67 + }; 68 + };
+35
entrypoints/popup/hooks/useTimer.ts
···
··· 1 + import { useCallback, useEffect, useState } from 'react'; 2 + 3 + export const useTimer = () => { 4 + const [status, setStatus] = useState<'Running' | 'Stopped'>('Stopped'); 5 + const [timer, setTimer] = useState<number | null>(null); 6 + const [loading, setLoading] = useState(true); 7 + 8 + const fetchAndSetTimer = useCallback(() => { 9 + browser.runtime.sendMessage({ action: 'getNextAlarm' }, (resp) => { 10 + setTimer(resp?.timeRemaining ?? null); 11 + }); 12 + }, []); 13 + 14 + useEffect(() => { 15 + browser.runtime.sendMessage({ action: 'getStatus' }, (response) => { 16 + setStatus(response?.status || 'Stopped'); 17 + setLoading(false); 18 + if (response?.status === 'Running') { 19 + fetchAndSetTimer(); 20 + } 21 + }); 22 + }, [fetchAndSetTimer]); 23 + 24 + useEffect(() => { 25 + if (status === 'Running') { 26 + fetchAndSetTimer(); // Ensure timer is set immediately on status change 27 + const interval = setInterval(fetchAndSetTimer, 1000); 28 + return () => clearInterval(interval); 29 + } else { 30 + setTimer(null); 31 + } 32 + }, [status, fetchAndSetTimer]); 33 + 34 + return { status, setStatus, timer, loading, setLoading }; 35 + };
+123
entrypoints/popup/hooks/useTokens.ts
···
··· 1 + import { useEffect, useState } from 'react'; 2 + import { 3 + extractTailnetNameFromToken, 4 + filterValidTokens, 5 + } from '$helpers/tokens'; 6 + import type { RuntimeMessage } from '$types/messages'; 7 + import type { Token } from '$types/tokens'; 8 + 9 + interface UseTokensOptions { 10 + setLoading?: (v: boolean) => void; 11 + setAlertMessage?: (v: string) => void; 12 + setShowAlert?: (v: boolean) => void; 13 + setClaimedToken?: (v: string) => void; 14 + } 15 + 16 + // Custom hook for managing tokens and claiming tokens 17 + export const useTokens = ({ 18 + setLoading, 19 + setAlertMessage, 20 + setShowAlert, 21 + setClaimedToken, 22 + }: UseTokensOptions = {}) => { 23 + const [tokens, setTokens] = useState<Token[]>([]); 24 + const [error, setError] = useState<string | null>(null); 25 + 26 + // Load and check for expired tokens once on mount 27 + useEffect(() => { 28 + const checkExpiredTokens = () => { 29 + browser.storage.local.get(['tailscaleTokens'], (result) => { 30 + const now = Date.now(); 31 + 32 + const validTokens: Token[] = filterValidTokens( 33 + result.tailscaleTokens || [], 34 + now, 35 + ); 36 + 37 + if (validTokens.length !== (result.tailscaleTokens || []).length) { 38 + browser.storage.local.set({ tailscaleTokens: validTokens }); 39 + } 40 + 41 + setTokens(validTokens); 42 + }); 43 + }; 44 + 45 + // Check immediately on mount 46 + checkExpiredTokens(); 47 + }, []); 48 + 49 + useEffect(() => { 50 + const listener = (message: RuntimeMessage<{ tokens?: Token[] }>) => { 51 + if (message.action === 'tokensUpdated') { 52 + const validTokens: Token[] = filterValidTokens(message.tokens || []); 53 + setTokens(validTokens); 54 + } 55 + }; 56 + browser.runtime.onMessage.addListener(listener); 57 + return () => browser.runtime.onMessage.removeListener(listener); 58 + }, []); 59 + 60 + // Claim token logic 61 + const handleClaimToken = async (tokenObj: Token) => { 62 + if (setLoading) setLoading(true); 63 + setError(null); 64 + 65 + try { 66 + const response = await browser.runtime.sendMessage({ 67 + action: 'claimToken', 68 + tcd: tokenObj.tcd || '', 69 + token: tokenObj.token, 70 + }); 71 + if (response?.redirected) { 72 + // DNS page opened, do not show error or alert 73 + return; 74 + } 75 + 76 + if (response?.error) { 77 + console.error(`[useTokens] Error claiming token:`, response.error); 78 + if (setAlertMessage && setShowAlert) { 79 + setAlertMessage(response.error); 80 + setShowAlert(true); 81 + } else { 82 + setError(response.error); 83 + } 84 + return; 85 + } 86 + 87 + if (setAlertMessage && setShowAlert && setClaimedToken) { 88 + setAlertMessage( 89 + `Offer claimed! Your tailnet name is now ${extractTailnetNameFromToken( 90 + tokenObj.token, 91 + )}.\nYou may need to refresh Tailscale to see the changes take effect.`, 92 + ); 93 + setShowAlert(true); 94 + setClaimedToken(tokenObj.token); 95 + return; 96 + } 97 + } catch (e) { 98 + const errorMsg = e instanceof Error ? e.message : 'Unknown error'; 99 + 100 + if (setAlertMessage && setShowAlert) { 101 + setAlertMessage(`Error claiming token: ${errorMsg}`); 102 + setShowAlert(true); 103 + } else { 104 + setError(errorMsg); 105 + } 106 + } finally { 107 + if (setLoading) setLoading(false); 108 + } 109 + }; 110 + 111 + // Remove token logic 112 + const handleRemoveToken = (tokenObj: Token) => { 113 + const updatedTokens = tokens.filter((t) => t.token !== tokenObj.token); 114 + setTokens(updatedTokens); 115 + browser.storage.local.set({ tailscaleTokens: updatedTokens }); 116 + }; 117 + 118 + return [ 119 + tokens, 120 + setTokens, 121 + { handleClaimToken, handleRemoveToken, error }, 122 + ] as const; 123 + };
+16
entrypoints/popup/hooks/useWords.ts
···
··· 1 + import { useEffect, useState } from 'react'; 2 + import { fetchAndCacheWords } from '$helpers/words'; 3 + 4 + // Custom hook for fetching and caching tailnet words 5 + export const useWords = () => { 6 + const [tails, setTails] = useState<string[]>([]); 7 + const [scales, setScales] = useState<string[]>([]); 8 + useEffect(() => { 9 + fetchAndCacheWords().then(({ tails, scales }) => { 10 + setTails(tails); 11 + setScales(scales); 12 + }); 13 + }, []); 14 + 15 + return { tails, scales }; 16 + };
+3
entrypoints/popup/index.css
···
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+13
entrypoints/popup/index.html
···
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>tailname</title> 7 + <meta name="manifest.type" content="browser_action" /> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="./main.tsx"></script> 12 + </body> 13 + </html>
+11
entrypoints/popup/main.tsx
···
··· 1 + import { StrictMode } from 'react'; 2 + import { createRoot } from 'react-dom/client'; 3 + import App from './App'; 4 + import './index.css'; 5 + 6 + // biome-ignore lint/style/noNonNullAssertion: Known non-null. 7 + createRoot(document.getElementById('root')!).render( 8 + <StrictMode> 9 + <App /> 10 + </StrictMode>, 11 + );
+65
entrypoints/popup/screens/MainScreen.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiListPlus, BiSolidKey } from 'react-icons/bi'; 3 + import ActionCard from '$components/ActionCard'; 4 + import HeaderSection from '$components/HeaderSection'; 5 + import StatusControls from '$components/StatusControls'; 6 + import type { Token } from '$types/tokens'; 7 + 8 + interface MainScreenProps { 9 + onShowWords: () => void; 10 + onShowTokens: () => void; 11 + status: string; 12 + timer: number | null; 13 + handleStart: () => void; 14 + handleStop: () => void; 15 + tailnetNames: string[]; 16 + tokens: Token[]; 17 + } 18 + 19 + const MainScreen: FC<MainScreenProps> = ({ 20 + onShowWords, 21 + onShowTokens, 22 + status, 23 + timer, 24 + handleStart, 25 + handleStop, 26 + tailnetNames, 27 + tokens, 28 + }) => ( 29 + <main className="rounded-none shadow-lg p-6 w-full h-full"> 30 + <HeaderSection /> 31 + 32 + <StatusControls 33 + handleStart={handleStart} 34 + handleStop={handleStop} 35 + status={status} 36 + tailnetNames={tailnetNames} 37 + timer={timer} 38 + redirectToTailnetList={onShowWords} 39 + /> 40 + 41 + <div className="flex flex-col items-center justify-center gap-6"> 42 + <div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full px-4"> 43 + <ActionCard 44 + icon={ 45 + <BiListPlus className="text-text" size={40} aria-hidden={true} /> 46 + } 47 + title="Manage tailnet words" 48 + subtitle="Search for specific tailnet names with keywords" 49 + onClick={onShowWords} 50 + /> 51 + 52 + <ActionCard 53 + icon={ 54 + <BiSolidKey className="text-text" size={40} aria-hidden={true} /> 55 + } 56 + title={`View tailnet offers ${tokens && tokens.length > 0 ? `(${tokens.length})` : ''}`} 57 + subtitle="Claim offers to use as custom tailnet names" 58 + onClick={onShowTokens} 59 + /> 60 + </div> 61 + </div> 62 + </main> 63 + ); 64 + 65 + export default MainScreen;
+83
entrypoints/popup/screens/TailnetWords.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiArrowBack, BiInfoCircle, BiListPlus } from 'react-icons/bi'; 3 + import HelperText from '$components/HelperText'; 4 + import TailnetInput from '$components/TailnetInput'; 5 + import TailnetList from '$components/TailnetList'; 6 + 7 + interface TailnetWordsScreenProps { 8 + tailnetNames: string[]; 9 + handleAddTailnet: () => void; 10 + handleRemoveTailnet: (name: string) => void; 11 + inputValue: string; 12 + setInputValue: (val: string) => void; 13 + isInputValid: boolean; 14 + loading: boolean; 15 + onBack: () => void; 16 + error: string | null; 17 + tails: string[]; 18 + scales: string[]; 19 + onShowWordList: () => void; 20 + } 21 + 22 + const TailnetWordsScreen: FC<TailnetWordsScreenProps> = ({ 23 + tailnetNames, 24 + handleAddTailnet, 25 + handleRemoveTailnet, 26 + inputValue, 27 + setInputValue, 28 + isInputValid, 29 + loading, 30 + onBack, 31 + error, 32 + tails, 33 + scales, 34 + onShowWordList, 35 + }) => ( 36 + <main className="rounded-none shadow-lg p-6 w-full h-full"> 37 + <div className="flex justify-between mb-4 items-center"> 38 + <button 39 + className="text-sm px-3 py-2 rounded-lg flex flex-row items-center bg-item border border-border text-text font-medium hover:bg-accent transition-colors hover:border-accent" 40 + type="button" 41 + onClick={onBack} 42 + > 43 + <BiArrowBack 44 + className="inline-block mr-1" 45 + size={20} 46 + aria-hidden={true} 47 + /> 48 + Back 49 + </button> 50 + 51 + <h2 className="text-2xl flex flex-row gap-2 font-semibold text-center text-text"> 52 + <BiListPlus className="text-text" size={35} aria-hidden={true} /> 53 + Manage tailnet words 54 + </h2> 55 + </div> 56 + 57 + <div className="pb-4"> 58 + <TailnetInput 59 + handleAddTailnet={handleAddTailnet} 60 + inputValue={inputValue} 61 + isInputValid={isInputValid} 62 + loading={loading} 63 + setInputValue={setInputValue} 64 + tailnetNames={tailnetNames} 65 + tails={tails} 66 + scales={scales} 67 + /> 68 + <div className="w-full text-center border border-border flex flex-row gap-2 justify-center text-text bg-item text-sm rounded-md mt-3 py-3 items-center"> 69 + <BiInfoCircle className="inline-block" size={20} aria-hidden={true} /> 70 + It's recommended to add multiple tailnet words to get tailnet offers 71 + faster. 72 + </div> 73 + <HelperText onShowWordList={onShowWordList} /> 74 + <TailnetList 75 + handleRemoveTailnet={handleRemoveTailnet} 76 + tailnetNames={tailnetNames} 77 + /> 78 + {error && <div className="text-danger mt-2">{error}</div>} 79 + </div> 80 + </main> 81 + ); 82 + 83 + export default TailnetWordsScreen;
+54
entrypoints/popup/screens/TokenList.tsx
···
··· 1 + import type { FC } from 'react'; 2 + import { BiArrowBack, BiSolidKey } from 'react-icons/bi'; 3 + import TokenList from '$components/TokenList'; 4 + import type { Token } from '$types/tokens'; 5 + 6 + interface TokenListScreenProps { 7 + tokens: Token[]; 8 + handleClaimToken: (tokenObj: Token) => Promise<void>; 9 + handleRemoveToken: (tokenObj: Token) => void; 10 + loading: boolean; 11 + error: string | null; 12 + onBack: () => void; 13 + } 14 + 15 + const TokenListScreen: FC<TokenListScreenProps> = ({ 16 + tokens, 17 + handleClaimToken, 18 + handleRemoveToken, 19 + loading, 20 + error, 21 + onBack, 22 + }) => ( 23 + <main className="rounded-none shadow-lg p-6 w-full h-full"> 24 + <div className="flex justify-between items-center mb-4"> 25 + <button 26 + className="text-sm px-3 py-2 rounded-lg flex flex-row items-center bg-item border border-border text-text font-medium hover:bg-accent transition-colors hover:border-accent" 27 + type="button" 28 + onClick={onBack} 29 + > 30 + <BiArrowBack 31 + className="inline-block mr-1" 32 + size={20} 33 + aria-hidden={true} 34 + /> 35 + Back 36 + </button> 37 + 38 + <h2 className="text-2xl font-semibold gap-2 flex flex-row text-text"> 39 + <BiSolidKey className="text-text" size={35} aria-hidden={true} /> 40 + Tailnet name offers 41 + </h2> 42 + </div> 43 + 44 + <TokenList 45 + error={error} 46 + handleClaimToken={handleClaimToken} 47 + handleRemoveToken={handleRemoveToken} 48 + loading={loading} 49 + tokens={tokens} 50 + /> 51 + </main> 52 + ); 53 + 54 + export default TokenListScreen;
+97
entrypoints/popup/screens/WordList.tsx
···
··· 1 + import { type FC, useState } from 'react'; 2 + import { BiArrowBack, BiTable } from 'react-icons/bi'; 3 + 4 + interface WordListScreenProps { 5 + tails: string[]; 6 + scales: string[]; 7 + onBack: () => void; 8 + } 9 + 10 + const WordListScreen: FC<WordListScreenProps> = ({ tails, scales, onBack }) => { 11 + const [search, setSearch] = useState(''); 12 + 13 + const sortedTails = [...tails].sort((a, b) => a.localeCompare(b)); 14 + const sortedScales = [...scales].sort((a, b) => a.localeCompare(b)); 15 + 16 + const filteredTails = sortedTails.filter((word) => 17 + word.toLowerCase().includes(search.toLowerCase()), 18 + ); 19 + const filteredScales = sortedScales.filter((word) => 20 + word.toLowerCase().includes(search.toLowerCase()), 21 + ); 22 + 23 + return ( 24 + <main className="rounded-none shadow-lg p-6 w-full h-full flex flex-col"> 25 + <div className="flex justify-between items-center mb-3"> 26 + <button 27 + className="text-sm px-3 py-2 rounded-lg flex flex-row items-center bg-item border border-border text-text font-medium hover:bg-accent transition-colors hover:border-accent" 28 + type="button" 29 + onClick={onBack} 30 + > 31 + <BiArrowBack 32 + className="inline-block mr-1" 33 + size={20} 34 + aria-hidden={true} 35 + /> 36 + Back 37 + </button> 38 + 39 + <h2 className="text-2xl flex flex-row gap-2 font-semibold text-center text-text"> 40 + <BiTable size={35} aria-hidden={true} /> 41 + Tailnet name word list 42 + </h2> 43 + </div> 44 + 45 + <input 46 + type="text" 47 + className="w-full border border-border rounded-lg px-3 py-1.5 mb-4 bg-input text-base text-text focus:border-accent focus:outline-none transition-colors" 48 + placeholder="Search for a word..." 49 + value={search} 50 + onChange={(e) => setSearch(e.target.value)} 51 + /> 52 + 53 + <div className="flex-1 overflow-y-auto border border-border rounded-lg bg-header p-4 [&::-webkit-scrollbar]:[width:0.5em] [&::-webkit-scrollbar-thumb]:bg-subtext"> 54 + <div className="flex gap-6"> 55 + <div className="flex-1"> 56 + <h3 className="text-lg font-bold mb-2 text-text">Tails</h3> 57 + {filteredTails.length > 0 ? ( 58 + <ul className="grid grid-cols-2 md:grid-cols-3 gap-2 mb-4"> 59 + {filteredTails.map((word) => ( 60 + <li 61 + key={word} 62 + className="text-text bg-item rounded px-2 py-1 text-base font-medium border border-border transition-colors" 63 + > 64 + {word} 65 + </li> 66 + ))} 67 + </ul> 68 + ) : ( 69 + <div className="text-subtext text-sm mb-4"> 70 + No tails words found. 71 + </div> 72 + )} 73 + </div> 74 + <div className="flex-1"> 75 + <h3 className="text-lg font-bold mb-2 text-text">Scales</h3> 76 + {filteredScales.length > 0 ? ( 77 + <ul className="grid grid-cols-2 md:grid-cols-3 gap-2"> 78 + {filteredScales.map((word) => ( 79 + <li 80 + key={word} 81 + className="text-text bg-item rounded px-2 py-1 text-base font-medium border border-border transition-colors" 82 + > 83 + {word} 84 + </li> 85 + ))} 86 + </ul> 87 + ) : ( 88 + <div className="text-subtext text-sm">No scales words found.</div> 89 + )} 90 + </div> 91 + </div> 92 + </div> 93 + </main> 94 + ); 95 + }; 96 + 97 + export default WordListScreen;
+46
handlers/alarm.ts
···
··· 1 + import { updateBadgeText } from '$background/badge'; 2 + import { log } from '$helpers/logging'; 3 + import type { Message } from '$types/messages'; 4 + 5 + export const handleGetNextAlarm = async ( 6 + _message: Message, 7 + sendResponse: (resp: unknown) => void, 8 + ) => { 9 + const alarm = await browser.alarms.get('checkOffersAlarm'); 10 + if (alarm?.scheduledTime) { 11 + const now = Date.now(); 12 + const timeRemaining = Math.max( 13 + 0, 14 + Math.round((alarm.scheduledTime - now) / 1000), 15 + ); 16 + sendResponse({ timeRemaining }); 17 + } else { 18 + sendResponse({ timeRemaining: null }); 19 + } 20 + }; 21 + 22 + export const handleSetAlarmInterval = async ( 23 + message: Message, 24 + sendResponse: (resp: unknown) => void, 25 + ) => { 26 + const interval = message.interval as number; 27 + const alarm = await browser.alarms.get('checkOffersAlarm'); 28 + 29 + if (alarm) { 30 + await browser.alarms.clear('checkOffersAlarm'); 31 + await browser.alarms.create('checkOffersAlarm', { 32 + periodInMinutes: interval / 60, 33 + }); 34 + 35 + log( 36 + `Alarm 'checkOffersAlarm' interval updated to ${interval} seconds (restarted).`, 37 + 'INFO', 38 + 'Alarms', 39 + ); 40 + 41 + updateBadgeText(); 42 + sendResponse({ success: true }); 43 + } else { 44 + sendResponse({ success: false, reason: 'Alarm not running.' }); 45 + } 46 + };
+44
handlers/alert.ts
···
··· 1 + import { log } from '$helpers/logging'; 2 + import { openDnsTabWithNotification } from '$helpers/notifications'; 3 + import type { Message } from '$types/messages'; 4 + 5 + const showCustomAlertWithAck = async ( 6 + _sendResponse: (resp: unknown) => void, 7 + ) => { 8 + browser.runtime.sendMessage({ 9 + action: 'showCustomAlert', 10 + message: 11 + "This extension will now redirect you to the Tailscale DNS page. This is sadly a limitation of what we can do with Tailscale's claim offers endpoint.\nAfter you're redirected, please re-open the extension window to finish the claim process.", 12 + }); 13 + 14 + const ackListener = async (msg: Message) => { 15 + if (msg.action === 'userAcknowledgedRedirect') { 16 + await openDnsTabWithNotification(); 17 + browser.runtime.onMessage.removeListener(ackListener); 18 + } 19 + }; 20 + 21 + browser.runtime.onMessage.addListener(ackListener); 22 + }; 23 + 24 + export const showDnsRedirectAlert = async ( 25 + sendResponse: (resp: unknown) => void, 26 + ) => { 27 + const activeTabs = await browser.tabs.query({ 28 + active: true, 29 + currentWindow: true, 30 + }); 31 + if (activeTabs && activeTabs.length > 0) { 32 + showCustomAlertWithAck(sendResponse); 33 + } else { 34 + await openDnsTabWithNotification(); 35 + } 36 + 37 + log( 38 + 'No Tailscale admin tab found. Now attempting to open https://login.tailscale.com/admin/dns in a new browser tab.', 39 + 'WARNING', 40 + 'Tokens', 41 + ); 42 + 43 + sendResponse({ success: true, redirected: true }); 44 + };
+10
handlers/eligibility.ts
···
··· 1 + import { checkEligibility } from '$helpers/eligibility'; 2 + import type { Message } from '$types/messages'; 3 + 4 + export const handleCheckEligibility = async ( 5 + _message: Message, 6 + sendResponse: (resp: unknown) => void, 7 + ) => { 8 + const result = await checkEligibility(); 9 + sendResponse({ eligibility: result }); 10 + };
+24
handlers/index.ts
···
··· 1 + import { handleGetNextAlarm, handleSetAlarmInterval } from '$handlers/alarm'; 2 + import { handleCheckEligibility } from '$handlers/eligibility'; 3 + import { 4 + handleGetStatus, 5 + handleStartCheck, 6 + handleStopCheck, 7 + } from '$handlers/status'; 8 + import { handleClaimToken } from '$handlers/token'; 9 + import type { Message } from '$types/messages'; 10 + 11 + const handlers: Record< 12 + string, 13 + (message: Message, sendResponse: (resp: unknown) => void) => void 14 + > = { 15 + checkEligibility: handleCheckEligibility, 16 + claimToken: handleClaimToken, 17 + getNextAlarm: handleGetNextAlarm, 18 + getStatus: handleGetStatus, 19 + setAlarmInterval: handleSetAlarmInterval, 20 + startCheck: handleStartCheck, 21 + stopCheck: handleStopCheck, 22 + }; 23 + 24 + export default handlers;
+40
handlers/status.ts
···
··· 1 + import { updateBadgeText } from '$background/badge'; 2 + import { defaultCheckOffersInterval } from '$config'; 3 + import { log } from '$helpers/logging'; 4 + import { checkTailscaleOffers } from '$helpers/offers'; 5 + import type { Message } from '$types/messages'; 6 + 7 + export const handleStartCheck = async ( 8 + _message: Message, 9 + sendResponse: (resp: unknown) => void, 10 + ) => { 11 + const result = await browser.storage.local.get(['checkInterval']); 12 + const interval = result.checkInterval || defaultCheckOffersInterval; // default 60 seconds 13 + browser.alarms.create('checkOffersAlarm', { periodInMinutes: interval / 60 }); 14 + log( 15 + `Checking offers process initialized. Interval set to check every ${interval} seconds.`, 16 + 'INFO', 17 + 'Alarms', 18 + ); 19 + await checkTailscaleOffers(); 20 + updateBadgeText(); 21 + sendResponse({ status: 'Running' }); 22 + }; 23 + 24 + export const handleStopCheck = async ( 25 + _message: Message, 26 + sendResponse: (resp: unknown) => void, 27 + ) => { 28 + browser.alarms.clear('checkOffersAlarm'); 29 + log('Checking offers process manually stopped by user.', 'INFO', 'Alarms'); 30 + updateBadgeText(); 31 + sendResponse({ status: 'Stopped' }); 32 + }; 33 + 34 + export const handleGetStatus = async ( 35 + _message: Message, 36 + sendResponse: (resp: unknown) => void, 37 + ) => { 38 + const alarm = await browser.alarms.get('checkOffersAlarm'); 39 + sendResponse({ status: alarm ? 'Running' : 'Stopped' }); 40 + };
+68
handlers/token.ts
···
··· 1 + import { updateBadgeText } from '$background/badge'; 2 + import { showDnsRedirectAlert } from '$handlers/alert'; 3 + import { log } from '$helpers/logging'; 4 + import type { Message } from '$types/messages'; 5 + 6 + const injectAndClaimToken = async ( 7 + tabId: number, 8 + tcd: unknown, 9 + token: unknown, 10 + sendResponse: (resp: unknown) => void, 11 + ) => { 12 + await browser.scripting.executeScript({ 13 + target: { tabId }, 14 + files: ['content-scripts/content.js'], 15 + }); 16 + 17 + try { 18 + const response = await browser.tabs.sendMessage(tabId, { 19 + action: 'claimToken', 20 + tcd, 21 + token, 22 + }); 23 + 24 + log( 25 + `Content script claim response: ${JSON.stringify(response)}`, 26 + 'DEBUG', 27 + 'Messaging', 28 + ); 29 + 30 + updateBadgeText(); 31 + sendResponse(response); 32 + } catch (err) { 33 + log( 34 + `Error while sending the claimToken alarm: ${err}`, 35 + 'ERROR', 36 + 'Messaging', 37 + ); 38 + updateBadgeText(); 39 + 40 + const errorMsg = err instanceof Error ? err.message : 'Unknown error'; 41 + sendResponse({ success: false, error: errorMsg }); 42 + } 43 + }; 44 + 45 + export const handleClaimToken = async ( 46 + message: Message, 47 + sendResponse: (resp: unknown) => void, 48 + ) => { 49 + const { tcd, token } = message; 50 + 51 + log( 52 + 'Forwarding claimToken request to content script. Awaiting response...', 53 + 'INFO', 54 + 'Messaging', 55 + ); 56 + 57 + const tabs = await browser.tabs.query({ 58 + url: 'https://login.tailscale.com/admin/dns', 59 + }); 60 + if (!tabs || tabs.length === 0) { 61 + await showDnsRedirectAlert(sendResponse); 62 + return; 63 + } 64 + 65 + // biome-ignore lint/style/noNonNullAssertion: We're force injecting here just in case the user already has the DNS page open. 66 + const tabId = tabs[0].id!; 67 + await injectAndClaimToken(tabId, tcd, token, sendResponse); 68 + };
+126
helpers/eligibility.ts
···
··· 1 + import { eligibilityCacheInterval } from '$config'; 2 + import { log } from '$helpers/logging'; 3 + import { fetchTailscaleOffers, getTailcontrolCookie } from '$helpers/offers'; 4 + import type { Eligibility, EligibilityApiResponse } from '$types/eligibility'; 5 + 6 + const eligibilityMap: Record<string, Eligibility> = { 7 + apiError: { 8 + eligible: false, 9 + reason: 'API error occurred.', 10 + id: 'api-error', 11 + error: 'API error', 12 + }, 13 + eligible: { 14 + eligible: true, 15 + reason: 'Eligible for new offers!', 16 + id: 'eligible', 17 + offerType: 'reoffer', 18 + }, 19 + 'forbidden - not admin': { 20 + eligible: false, 21 + reason: 22 + 'User does not have the Admin role in Tailscale, and thus has insufficient privileges.', 23 + id: 'not-admin', 24 + error: 'forbidden - not admin', 25 + }, 26 + 'forbidden - not logged in': { 27 + eligible: false, 28 + reason: 'User is not logged in to Tailscale.', 29 + id: 'not-logged-in', 30 + error: 'forbidden - not logged in', 31 + }, 32 + locked: { 33 + eligible: false, 34 + reason: 'User already used their tailnet name for an HTTPS certificate.', 35 + id: 'custom-tailnet', 36 + offerType: 'locked', 37 + }, 38 + unknown: { 39 + eligible: false, 40 + reason: 'Unknown eligibility state.', 41 + id: 'unknown', 42 + }, 43 + }; 44 + 45 + export const parseEligibilityResponse = ( 46 + body: EligibilityApiResponse, 47 + ): Eligibility => { 48 + if (body.error) { 49 + return { 50 + ...eligibilityMap.apiError, 51 + reason: `API error: ${body.error}`, 52 + error: body.error, 53 + }; 54 + } 55 + 56 + if (body.error && eligibilityMap[body.error]) 57 + return eligibilityMap[body.error]; 58 + 59 + if (body.data?.offerType === 'locked') return eligibilityMap.locked; 60 + if (body.data?.offerType === 'reoffer') return eligibilityMap.eligible; 61 + 62 + return { 63 + ...eligibilityMap.unknown, 64 + offerType: body.data?.offerType, 65 + }; 66 + }; 67 + 68 + export const handleEligibilityResult = (eligibility: Eligibility) => { 69 + eligibility.checkedAt = Date.now(); 70 + browser.storage.local.set({ eligibility }); 71 + }; 72 + 73 + export const checkEligibility = async (): Promise<Eligibility> => { 74 + log( 75 + 'Checking if the user is eligible for Tailscale offers...', 76 + 'INFO', 77 + 'Eligibility', 78 + ); 79 + let eligibility: Eligibility = eligibilityMap.unknown; 80 + 81 + const cacheDurationMs = eligibilityCacheInterval * 1000; 82 + const cached = await browser.storage.local.get('eligibility'); 83 + if (cached.eligibility?.checkedAt) { 84 + const now = Date.now(); 85 + if (now - cached.eligibility.checkedAt < cacheDurationMs) { 86 + log('Using cached eligibility response.', 'INFO', 'Eligibility'); 87 + return cached.eligibility; 88 + } 89 + } 90 + 91 + try { 92 + const cookie = await getTailcontrolCookie(); 93 + if (!cookie) { 94 + eligibility = eligibilityMap['forbidden - not logged in']; 95 + handleEligibilityResult(eligibility); 96 + return eligibility; 97 + } 98 + 99 + const cookieValue = cookie.value; 100 + const response = await fetchTailscaleOffers(cookieValue); 101 + const body = await response.json(); 102 + console.log(body); 103 + eligibility = parseEligibilityResponse(body); 104 + handleEligibilityResult(eligibility); 105 + 106 + return eligibility; 107 + } catch (e) { 108 + const errorMsg = e instanceof Error ? e.message : 'Unknown error'; 109 + 110 + eligibility = { 111 + ...eligibilityMap.unknown, 112 + reason: errorMsg, 113 + error: errorMsg, 114 + }; 115 + 116 + handleEligibilityResult(eligibility); 117 + 118 + return eligibility; 119 + } finally { 120 + log( 121 + `Eligibility check result: ${eligibility.eligible} (${eligibility.reason})`, 122 + 'INFO', 123 + 'Eligibility', 124 + ); 125 + } 126 + };
+31
helpers/logging.ts
···
··· 1 + type LogSeverity = 'INFO' | 'WARNING' | 'DEBUG' | 'ERROR'; 2 + type LogCategory = 3 + | 'Alarms' 4 + | 'Notifications' 5 + | 'Cookies' 6 + | 'Tokens' 7 + | 'Eligibility' 8 + | 'Offers' 9 + | 'Storage' 10 + | 'Messaging' 11 + | 'Setup' 12 + | 'General'; 13 + 14 + const severityEmoji: Record<LogSeverity, string> = { 15 + INFO: 'ℹ️', 16 + WARNING: '⚠️', 17 + DEBUG: '🐛', 18 + ERROR: '❌', 19 + }; 20 + 21 + export const log = ( 22 + message: string, 23 + severity: LogSeverity = 'INFO', 24 + category: LogCategory = 'General', 25 + ) => { 26 + const timestamp = new Date().toLocaleTimeString(); 27 + const emoji = severityEmoji[severity] || ''; 28 + console.log( 29 + `${emoji} [${timestamp}] [${severity}] [${category}]: ${message}`, 30 + ); 31 + };
+76
helpers/notifications.ts
···
··· 1 + import { log } from '$helpers/logging'; 2 + import type { DefaultStorageOptions } from '$types/storage'; 3 + 4 + export const openDnsTabWithNotification = async () => { 5 + await browser.tabs.create({ url: 'https://login.tailscale.com/admin/dns' }); 6 + browser.notifications.create({ 7 + type: 'basic', 8 + iconUrl: browser.runtime.getURL('/icons/128.png'), 9 + title: 'Tailscale DNS Page Opened', 10 + message: 11 + 'Please re-open the extension window to continue the claim process.', 12 + }); 13 + }; 14 + 15 + export const buildSystemNotificationOptions = ( 16 + message: string, 17 + ): Browser.notifications.NotificationCreateOptions => ({ 18 + type: 'basic', 19 + iconUrl: browser.runtime.getURL('/icons/128.png'), 20 + title: 'Tailnet name offer found!', 21 + message, 22 + }); 23 + 24 + export const buildNtfyNotificationOptions = ( 25 + token: string, 26 + ): Record<string, string> => ({ 27 + Title: `Tailnet name offer found!`, 28 + Priority: '3', 29 + Tags: 'key', 30 + ...(token ? { Authorization: `Bearer ${token}` } : {}), 31 + }); 32 + 33 + export const notifyNewTokens = async ( 34 + newTokens: string[], 35 + result: DefaultStorageOptions, 36 + ) => { 37 + const tailnetNamesOnly = newTokens.map((token) => token.split('/')[0]); 38 + const messageText = `New offer${ 39 + newTokens.length > 1 ? 's' : '' 40 + } found: ${tailnetNamesOnly.join(', ')}. Hurry, it expires in 5 minutes!`; 41 + 42 + if (result.useSystemPush) { 43 + try { 44 + await browser.notifications.create( 45 + buildSystemNotificationOptions(messageText), 46 + ); 47 + } catch (err) { 48 + log( 49 + `Failed to create notification for new offers: ${err}`, 50 + 'ERROR', 51 + 'Notifications', 52 + ); 53 + } 54 + } 55 + 56 + if (result.useNtfyPush) { 57 + const ntfyUrl = (result.ntfyUrl || '').replace(/\/$/, ''); 58 + const ntfyTopic = result.ntfyTopic || ''; 59 + const ntfyToken = result.ntfyToken || ''; 60 + if (ntfyUrl && ntfyTopic) { 61 + try { 62 + await fetch(`${ntfyUrl}/${ntfyTopic}`, { 63 + method: 'POST', 64 + headers: buildNtfyNotificationOptions(ntfyToken), 65 + body: messageText, 66 + }); 67 + } catch (err) { 68 + log( 69 + `Failed to send ntfy notification for new offers: ${err}`, 70 + 'ERROR', 71 + 'Notifications', 72 + ); 73 + } 74 + } 75 + } 76 + };
+103
helpers/offers.ts
···
··· 1 + import { log } from '$helpers/logging'; 2 + import { getTailnetNames, validateTailnetNames } from '$helpers/tailnet'; 3 + import { extractMatchingTokens, storeTokens } from '$helpers/tokens'; 4 + import type { Offer } from '$types/tokens'; 5 + 6 + export const getTailcontrolCookie = async (): Promise<{ 7 + value: string; 8 + } | null> => { 9 + return await browser.cookies.get({ 10 + url: 'https://login.tailscale.com', 11 + name: 'tailcontrol', 12 + }); 13 + }; 14 + 15 + export const fetchTailscaleOffers = async (cookieValue: string) => { 16 + return await fetch('https://login.tailscale.com/admin/api/tcd/offers', { 17 + headers: { 18 + accept: 'application/json, text/plain, */*', 19 + 'accept-language': 'en-US,en;q=0.9', 20 + 'cache-control': 'no-cache', 21 + pragma: 'no-cache', 22 + cookie: `tailcontrol=${cookieValue}; tailwww-ui=true`, 23 + }, 24 + referrer: 'https://login.tailscale.com/admin/dns', 25 + referrerPolicy: 'strict-origin-when-cross-origin', 26 + method: 'GET', 27 + mode: 'cors', 28 + }); 29 + }; 30 + 31 + const handleCheckOffersError = (errMsg: string) => { 32 + log(errMsg, 'ERROR', 'Offers'); 33 + log( 34 + 'Checking offers process finished due to an error. See line above.', 35 + 'ERROR', 36 + 'Offers', 37 + ); 38 + 39 + browser.alarms.clear('checkOffersAlarm'); 40 + }; 41 + 42 + export const checkTailscaleOffers = async () => { 43 + log( 44 + 'Checking offers process started. Searching Tailscale for tailnet name offers...', 45 + 'INFO', 46 + 'Offers', 47 + ); 48 + let tailnetNames: string[] = []; 49 + 50 + try { 51 + tailnetNames = await getTailnetNames(); 52 + } catch { 53 + log( 54 + 'Failed to get tailnetNames from storage. Defaulting to empty array.', 55 + 'WARNING', 56 + 'Offers', 57 + ); 58 + } 59 + 60 + if (!validateTailnetNames(tailnetNames)) { 61 + handleCheckOffersError( 62 + 'No tailnet keywords set. User needs to add at least one tailnet keyword.', 63 + ); 64 + return; 65 + } 66 + 67 + try { 68 + const cookie = await getTailcontrolCookie(); 69 + if (!cookie) { 70 + handleCheckOffersError( 71 + 'Tailcontrol cookie not found. User needs to log in to Tailscale.', 72 + ); 73 + return; 74 + } 75 + 76 + const cookieValue = cookie.value; 77 + const response = await fetchTailscaleOffers(cookieValue); 78 + const body: { data?: Offer } = await response.json(); 79 + 80 + if (body.data?.tcds) { 81 + const tokens = extractMatchingTokens(body.data.tcds, tailnetNames); 82 + 83 + if (tokens.length > 0) { 84 + log( 85 + `Found matching tokens: ${JSON.stringify(tokens)}`, 86 + 'INFO', 87 + 'Offers', 88 + ); 89 + 90 + storeTokens(tokens); 91 + } else { 92 + log( 93 + "No matching tailnet name offers found that match the user's defined keywords.", 94 + 'INFO', 95 + 'Offers', 96 + ); 97 + } 98 + } 99 + } catch (e) { 100 + const errorMsg = e instanceof Error ? e.message : 'Unknown error'; 101 + handleCheckOffersError(errorMsg || 'Unknown error'); 102 + } 103 + };
+11
helpers/tailnet.ts
···
··· 1 + export const getTailnetNames = async (): Promise<string[]> => { 2 + return await new Promise((resolve) => { 3 + browser.storage.local.get(['tailnetNames'], (result) => { 4 + resolve(Array.isArray(result.tailnetNames) ? result.tailnetNames : []); 5 + }); 6 + }); 7 + }; 8 + 9 + export const validateTailnetNames = (tailnetNames: string[]) => { 10 + return Array.isArray(tailnetNames) && tailnetNames.length > 0; 11 + };
+100
helpers/tokens.ts
···
··· 1 + import { tokenValidityInterval } from '$config'; 2 + import { log } from '$helpers/logging'; 3 + import { notifyNewTokens } from '$helpers/notifications'; 4 + 5 + import type { StoreTokensResult, Token } from '$types/tokens'; 6 + 7 + export const extractMatchingTokens = ( 8 + tcds: Token[], 9 + tailnetNames: string[], 10 + ) => { 11 + return tcds 12 + .filter(({ tcd }) => { 13 + const tcdName = tcd.replace(/\.ts\.net$/, ''); 14 + return tailnetNames.some((name) => { 15 + if (name.includes('-')) { 16 + // Combo: must match exactly 17 + return tcdName === name; 18 + } 19 + 20 + // Single word: match any part 21 + return tcdName.split('-').includes(name); 22 + }); 23 + }) 24 + .map(({ token }) => token); 25 + }; 26 + 27 + export const storeTokens = async (tokens: string[]) => { 28 + try { 29 + const result: StoreTokensResult = await browser.storage.local.get([ 30 + 'tailscaleTokens', 31 + 'useSystemPush', 32 + 'useNtfyPush', 33 + 'ntfyUrl', 34 + 'ntfyTopic', 35 + 'ntfyToken', 36 + ]); 37 + 38 + const now = Date.now(); 39 + const existingTokens = Array.isArray(result.tailscaleTokens) 40 + ? result.tailscaleTokens 41 + : []; 42 + const validTokens = filterValidTokens(existingTokens, now); 43 + 44 + if (validTokens.length !== existingTokens.length) { 45 + try { 46 + await browser.storage.local.set({ tailscaleTokens: validTokens }); 47 + } catch (err) { 48 + log( 49 + `Failed to update valid tokens in storage: ${err}`, 50 + 'ERROR', 51 + 'Tokens', 52 + ); 53 + } 54 + } 55 + 56 + const newTokens = tokens.filter( 57 + (token) => !validTokens.some((t: Token) => t.token === token), 58 + ); 59 + const updatedTokens = validTokens.concat( 60 + newTokens.map((token) => ({ token, timestamp: now }) as Token), 61 + ); 62 + 63 + try { 64 + await browser.storage.local.set({ tailscaleTokens: updatedTokens }); 65 + log('Tokens appended to storage with timestamp.', 'INFO', 'Tokens'); 66 + await browser.runtime.sendMessage({ 67 + action: 'tokensUpdated', 68 + tokens: updatedTokens, 69 + }); 70 + } catch (err) { 71 + log( 72 + `Failed to update tokens in storage or send message: ${err}`, 73 + 'ERROR', 74 + 'Tokens', 75 + ); 76 + } 77 + 78 + if (newTokens.length > 0) { 79 + await notifyNewTokens(newTokens, result); 80 + } 81 + } catch (err) { 82 + log(`Failed to store tokens: ${err}`, 'ERROR', 'Tokens'); 83 + } 84 + }; 85 + 86 + export const extractTailnetNameFromToken = (token: string) => { 87 + const match = token.match(/^([^.]+(?:-[^.]+)?)\.ts\.net/); 88 + return match ? match[0] : token; 89 + }; 90 + 91 + export const filterValidTokens = ( 92 + tokens: Token[], 93 + now: number = Date.now(), 94 + ): Token[] => { 95 + return (tokens || []).filter( 96 + (t) => 97 + typeof t.timestamp === 'number' && 98 + now - t.timestamp < tokenValidityInterval * 1000, 99 + ); 100 + };
+55
helpers/words.ts
···
··· 1 + import { tailscaleWordsUrls } from '$config'; 2 + 3 + export const fetchAndCacheWords = (): Promise<{ 4 + tails: string[]; 5 + scales: string[]; 6 + }> => { 7 + return new Promise((resolve) => { 8 + browser.storage.local.get( 9 + ['tailnetTails', 'tailnetScales'], 10 + async (result) => { 11 + if ( 12 + Array.isArray(result.tailnetTails) && 13 + result.tailnetTails.length > 0 && 14 + Array.isArray(result.tailnetScales) && 15 + result.tailnetScales.length > 0 16 + ) { 17 + resolve({ tails: result.tailnetTails, scales: result.tailnetScales }); 18 + return; 19 + } 20 + 21 + try { 22 + const responses = await Promise.all( 23 + tailscaleWordsUrls.map((url) => fetch(url)), 24 + ); 25 + const texts = await Promise.all(responses.map((r) => r.text())); 26 + const tails = Array.from( 27 + new Set( 28 + texts[0] 29 + .split(/\r?\n/) 30 + .map((w) => w.trim()) 31 + .filter((w) => w.length > 0), 32 + ), 33 + ); 34 + const scales = Array.from( 35 + new Set( 36 + texts[1] 37 + .split(/\r?\n/) 38 + .map((w) => w.trim()) 39 + .filter((w) => w.length > 0), 40 + ), 41 + ); 42 + 43 + browser.storage.local.set( 44 + { tailnetTails: tails, tailnetScales: scales }, 45 + () => { 46 + resolve({ tails, scales }); 47 + }, 48 + ); 49 + } catch { 50 + resolve({ tails: [], scales: [] }); 51 + } 52 + }, 53 + ); 54 + }); 55 + };
+36
package.json
···
··· 1 + { 2 + "name": "tailname", 3 + "description": "Search for custom tailnet name offers with keywords.", 4 + "author": "Chloe Arciniega <chloe@sapphic.moe>", 5 + "private": true, 6 + "version": "1.0.0", 7 + "type": "module", 8 + "license": "zlib", 9 + "scripts": { 10 + "dev": "wxt", 11 + "dev:firefox": "wxt -b firefox", 12 + "build": "wxt build", 13 + "build:firefox": "wxt build -b firefox", 14 + "zip": "wxt zip", 15 + "zip:firefox": "wxt zip -b firefox", 16 + "compile": "tsc --noEmit", 17 + "postinstall": "wxt prepare" 18 + }, 19 + "dependencies": { 20 + "react": "^19.1.0", 21 + "react-dom": "^19.1.0", 22 + "react-icons": "^5.5.0" 23 + }, 24 + "devDependencies": { 25 + "@biomejs/biome": "2.2.4", 26 + "@types/react": "^19.1.13", 27 + "@types/react-dom": "^19.1.9", 28 + "@wxt-dev/auto-icons": "^1.1.0", 29 + "@wxt-dev/module-react": "^1.1.5", 30 + "autoprefixer": "^10.4.21", 31 + "postcss": "^8.5.6", 32 + "tailwindcss": "3.4.1", 33 + "typescript": "^5.8.3", 34 + "wxt": "^0.20.11" 35 + } 36 + }
+4839
pnpm-lock.yaml
···
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + react: 12 + specifier: ^19.1.0 13 + version: 19.1.1 14 + react-dom: 15 + specifier: ^19.1.0 16 + version: 19.1.1(react@19.1.1) 17 + react-icons: 18 + specifier: ^5.5.0 19 + version: 5.5.0(react@19.1.1) 20 + devDependencies: 21 + '@biomejs/biome': 22 + specifier: 2.2.4 23 + version: 2.2.4 24 + '@types/react': 25 + specifier: ^19.1.13 26 + version: 19.1.13 27 + '@types/react-dom': 28 + specifier: ^19.1.9 29 + version: 19.1.9(@types/react@19.1.13) 30 + '@wxt-dev/auto-icons': 31 + specifier: ^1.1.0 32 + version: 1.1.0(wxt@0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1)) 33 + '@wxt-dev/module-react': 34 + specifier: ^1.1.5 35 + version: 1.1.5(vite@7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1))(wxt@0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1)) 36 + autoprefixer: 37 + specifier: ^10.4.21 38 + version: 10.4.21(postcss@8.5.6) 39 + postcss: 40 + specifier: ^8.5.6 41 + version: 8.5.6 42 + tailwindcss: 43 + specifier: 3.4.1 44 + version: 3.4.1 45 + typescript: 46 + specifier: ^5.8.3 47 + version: 5.9.2 48 + wxt: 49 + specifier: ^0.20.11 50 + version: 0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1) 51 + 52 + packages: 53 + 54 + '@1natsu/wait-element@4.1.2': 55 + resolution: {integrity: sha512-qWxSJD+Q5b8bKOvESFifvfZ92DuMsY+03SBNjTO34ipJLP6mZ9yK4bQz/vlh48aEQXoJfaZBqUwKL5BdI5iiWw==} 56 + 57 + '@aklinker1/rollup-plugin-visualizer@5.12.0': 58 + resolution: {integrity: sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==} 59 + engines: {node: '>=14'} 60 + hasBin: true 61 + peerDependencies: 62 + rollup: 2.x || 3.x || 4.x 63 + peerDependenciesMeta: 64 + rollup: 65 + optional: true 66 + 67 + '@alloc/quick-lru@5.2.0': 68 + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 69 + engines: {node: '>=10'} 70 + 71 + '@babel/code-frame@7.27.1': 72 + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 73 + engines: {node: '>=6.9.0'} 74 + 75 + '@babel/compat-data@7.28.4': 76 + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} 77 + engines: {node: '>=6.9.0'} 78 + 79 + '@babel/core@7.28.4': 80 + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} 81 + engines: {node: '>=6.9.0'} 82 + 83 + '@babel/generator@7.28.3': 84 + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} 85 + engines: {node: '>=6.9.0'} 86 + 87 + '@babel/helper-compilation-targets@7.27.2': 88 + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} 89 + engines: {node: '>=6.9.0'} 90 + 91 + '@babel/helper-globals@7.28.0': 92 + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 93 + engines: {node: '>=6.9.0'} 94 + 95 + '@babel/helper-module-imports@7.27.1': 96 + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} 97 + engines: {node: '>=6.9.0'} 98 + 99 + '@babel/helper-module-transforms@7.28.3': 100 + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} 101 + engines: {node: '>=6.9.0'} 102 + peerDependencies: 103 + '@babel/core': ^7.0.0 104 + 105 + '@babel/helper-plugin-utils@7.27.1': 106 + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} 107 + engines: {node: '>=6.9.0'} 108 + 109 + '@babel/helper-string-parser@7.27.1': 110 + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 111 + engines: {node: '>=6.9.0'} 112 + 113 + '@babel/helper-validator-identifier@7.27.1': 114 + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} 115 + engines: {node: '>=6.9.0'} 116 + 117 + '@babel/helper-validator-option@7.27.1': 118 + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} 119 + engines: {node: '>=6.9.0'} 120 + 121 + '@babel/helpers@7.28.4': 122 + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} 123 + engines: {node: '>=6.9.0'} 124 + 125 + '@babel/parser@7.28.4': 126 + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} 127 + engines: {node: '>=6.0.0'} 128 + hasBin: true 129 + 130 + '@babel/plugin-transform-react-jsx-self@7.27.1': 131 + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} 132 + engines: {node: '>=6.9.0'} 133 + peerDependencies: 134 + '@babel/core': ^7.0.0-0 135 + 136 + '@babel/plugin-transform-react-jsx-source@7.27.1': 137 + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} 138 + engines: {node: '>=6.9.0'} 139 + peerDependencies: 140 + '@babel/core': ^7.0.0-0 141 + 142 + '@babel/runtime@7.28.2': 143 + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} 144 + engines: {node: '>=6.9.0'} 145 + 146 + '@babel/template@7.27.2': 147 + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} 148 + engines: {node: '>=6.9.0'} 149 + 150 + '@babel/traverse@7.28.4': 151 + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} 152 + engines: {node: '>=6.9.0'} 153 + 154 + '@babel/types@7.28.4': 155 + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} 156 + engines: {node: '>=6.9.0'} 157 + 158 + '@biomejs/biome@2.2.4': 159 + resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==} 160 + engines: {node: '>=14.21.3'} 161 + hasBin: true 162 + 163 + '@biomejs/cli-darwin-arm64@2.2.4': 164 + resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==} 165 + engines: {node: '>=14.21.3'} 166 + cpu: [arm64] 167 + os: [darwin] 168 + 169 + '@biomejs/cli-darwin-x64@2.2.4': 170 + resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==} 171 + engines: {node: '>=14.21.3'} 172 + cpu: [x64] 173 + os: [darwin] 174 + 175 + '@biomejs/cli-linux-arm64-musl@2.2.4': 176 + resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==} 177 + engines: {node: '>=14.21.3'} 178 + cpu: [arm64] 179 + os: [linux] 180 + 181 + '@biomejs/cli-linux-arm64@2.2.4': 182 + resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} 183 + engines: {node: '>=14.21.3'} 184 + cpu: [arm64] 185 + os: [linux] 186 + 187 + '@biomejs/cli-linux-x64-musl@2.2.4': 188 + resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} 189 + engines: {node: '>=14.21.3'} 190 + cpu: [x64] 191 + os: [linux] 192 + 193 + '@biomejs/cli-linux-x64@2.2.4': 194 + resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} 195 + engines: {node: '>=14.21.3'} 196 + cpu: [x64] 197 + os: [linux] 198 + 199 + '@biomejs/cli-win32-arm64@2.2.4': 200 + resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} 201 + engines: {node: '>=14.21.3'} 202 + cpu: [arm64] 203 + os: [win32] 204 + 205 + '@biomejs/cli-win32-x64@2.2.4': 206 + resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==} 207 + engines: {node: '>=14.21.3'} 208 + cpu: [x64] 209 + os: [win32] 210 + 211 + '@devicefarmer/adbkit-logcat@2.1.3': 212 + resolution: {integrity: sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==} 213 + engines: {node: '>= 4'} 214 + 215 + '@devicefarmer/adbkit-monkey@1.2.1': 216 + resolution: {integrity: sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==} 217 + engines: {node: '>= 0.10.4'} 218 + 219 + '@devicefarmer/adbkit@3.3.8': 220 + resolution: {integrity: sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==} 221 + engines: {node: '>= 0.10.4'} 222 + hasBin: true 223 + 224 + '@emnapi/runtime@1.4.5': 225 + resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} 226 + 227 + '@esbuild/aix-ppc64@0.25.9': 228 + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} 229 + engines: {node: '>=18'} 230 + cpu: [ppc64] 231 + os: [aix] 232 + 233 + '@esbuild/android-arm64@0.25.9': 234 + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} 235 + engines: {node: '>=18'} 236 + cpu: [arm64] 237 + os: [android] 238 + 239 + '@esbuild/android-arm@0.25.9': 240 + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} 241 + engines: {node: '>=18'} 242 + cpu: [arm] 243 + os: [android] 244 + 245 + '@esbuild/android-x64@0.25.9': 246 + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} 247 + engines: {node: '>=18'} 248 + cpu: [x64] 249 + os: [android] 250 + 251 + '@esbuild/darwin-arm64@0.25.9': 252 + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} 253 + engines: {node: '>=18'} 254 + cpu: [arm64] 255 + os: [darwin] 256 + 257 + '@esbuild/darwin-x64@0.25.9': 258 + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} 259 + engines: {node: '>=18'} 260 + cpu: [x64] 261 + os: [darwin] 262 + 263 + '@esbuild/freebsd-arm64@0.25.9': 264 + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} 265 + engines: {node: '>=18'} 266 + cpu: [arm64] 267 + os: [freebsd] 268 + 269 + '@esbuild/freebsd-x64@0.25.9': 270 + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} 271 + engines: {node: '>=18'} 272 + cpu: [x64] 273 + os: [freebsd] 274 + 275 + '@esbuild/linux-arm64@0.25.9': 276 + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} 277 + engines: {node: '>=18'} 278 + cpu: [arm64] 279 + os: [linux] 280 + 281 + '@esbuild/linux-arm@0.25.9': 282 + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} 283 + engines: {node: '>=18'} 284 + cpu: [arm] 285 + os: [linux] 286 + 287 + '@esbuild/linux-ia32@0.25.9': 288 + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} 289 + engines: {node: '>=18'} 290 + cpu: [ia32] 291 + os: [linux] 292 + 293 + '@esbuild/linux-loong64@0.25.9': 294 + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} 295 + engines: {node: '>=18'} 296 + cpu: [loong64] 297 + os: [linux] 298 + 299 + '@esbuild/linux-mips64el@0.25.9': 300 + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} 301 + engines: {node: '>=18'} 302 + cpu: [mips64el] 303 + os: [linux] 304 + 305 + '@esbuild/linux-ppc64@0.25.9': 306 + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} 307 + engines: {node: '>=18'} 308 + cpu: [ppc64] 309 + os: [linux] 310 + 311 + '@esbuild/linux-riscv64@0.25.9': 312 + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} 313 + engines: {node: '>=18'} 314 + cpu: [riscv64] 315 + os: [linux] 316 + 317 + '@esbuild/linux-s390x@0.25.9': 318 + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} 319 + engines: {node: '>=18'} 320 + cpu: [s390x] 321 + os: [linux] 322 + 323 + '@esbuild/linux-x64@0.25.9': 324 + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} 325 + engines: {node: '>=18'} 326 + cpu: [x64] 327 + os: [linux] 328 + 329 + '@esbuild/netbsd-arm64@0.25.9': 330 + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} 331 + engines: {node: '>=18'} 332 + cpu: [arm64] 333 + os: [netbsd] 334 + 335 + '@esbuild/netbsd-x64@0.25.9': 336 + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} 337 + engines: {node: '>=18'} 338 + cpu: [x64] 339 + os: [netbsd] 340 + 341 + '@esbuild/openbsd-arm64@0.25.9': 342 + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} 343 + engines: {node: '>=18'} 344 + cpu: [arm64] 345 + os: [openbsd] 346 + 347 + '@esbuild/openbsd-x64@0.25.9': 348 + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} 349 + engines: {node: '>=18'} 350 + cpu: [x64] 351 + os: [openbsd] 352 + 353 + '@esbuild/openharmony-arm64@0.25.9': 354 + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} 355 + engines: {node: '>=18'} 356 + cpu: [arm64] 357 + os: [openharmony] 358 + 359 + '@esbuild/sunos-x64@0.25.9': 360 + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} 361 + engines: {node: '>=18'} 362 + cpu: [x64] 363 + os: [sunos] 364 + 365 + '@esbuild/win32-arm64@0.25.9': 366 + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} 367 + engines: {node: '>=18'} 368 + cpu: [arm64] 369 + os: [win32] 370 + 371 + '@esbuild/win32-ia32@0.25.9': 372 + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} 373 + engines: {node: '>=18'} 374 + cpu: [ia32] 375 + os: [win32] 376 + 377 + '@esbuild/win32-x64@0.25.9': 378 + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} 379 + engines: {node: '>=18'} 380 + cpu: [x64] 381 + os: [win32] 382 + 383 + '@img/sharp-darwin-arm64@0.34.3': 384 + resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} 385 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 386 + cpu: [arm64] 387 + os: [darwin] 388 + 389 + '@img/sharp-darwin-x64@0.34.3': 390 + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} 391 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 392 + cpu: [x64] 393 + os: [darwin] 394 + 395 + '@img/sharp-libvips-darwin-arm64@1.2.0': 396 + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} 397 + cpu: [arm64] 398 + os: [darwin] 399 + 400 + '@img/sharp-libvips-darwin-x64@1.2.0': 401 + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} 402 + cpu: [x64] 403 + os: [darwin] 404 + 405 + '@img/sharp-libvips-linux-arm64@1.2.0': 406 + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} 407 + cpu: [arm64] 408 + os: [linux] 409 + 410 + '@img/sharp-libvips-linux-arm@1.2.0': 411 + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} 412 + cpu: [arm] 413 + os: [linux] 414 + 415 + '@img/sharp-libvips-linux-ppc64@1.2.0': 416 + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} 417 + cpu: [ppc64] 418 + os: [linux] 419 + 420 + '@img/sharp-libvips-linux-s390x@1.2.0': 421 + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} 422 + cpu: [s390x] 423 + os: [linux] 424 + 425 + '@img/sharp-libvips-linux-x64@1.2.0': 426 + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} 427 + cpu: [x64] 428 + os: [linux] 429 + 430 + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': 431 + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} 432 + cpu: [arm64] 433 + os: [linux] 434 + 435 + '@img/sharp-libvips-linuxmusl-x64@1.2.0': 436 + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} 437 + cpu: [x64] 438 + os: [linux] 439 + 440 + '@img/sharp-linux-arm64@0.34.3': 441 + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} 442 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 443 + cpu: [arm64] 444 + os: [linux] 445 + 446 + '@img/sharp-linux-arm@0.34.3': 447 + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} 448 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 449 + cpu: [arm] 450 + os: [linux] 451 + 452 + '@img/sharp-linux-ppc64@0.34.3': 453 + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} 454 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 455 + cpu: [ppc64] 456 + os: [linux] 457 + 458 + '@img/sharp-linux-s390x@0.34.3': 459 + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} 460 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 461 + cpu: [s390x] 462 + os: [linux] 463 + 464 + '@img/sharp-linux-x64@0.34.3': 465 + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} 466 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 467 + cpu: [x64] 468 + os: [linux] 469 + 470 + '@img/sharp-linuxmusl-arm64@0.34.3': 471 + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} 472 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 473 + cpu: [arm64] 474 + os: [linux] 475 + 476 + '@img/sharp-linuxmusl-x64@0.34.3': 477 + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} 478 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 479 + cpu: [x64] 480 + os: [linux] 481 + 482 + '@img/sharp-wasm32@0.34.3': 483 + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} 484 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 485 + cpu: [wasm32] 486 + 487 + '@img/sharp-win32-arm64@0.34.3': 488 + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} 489 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 490 + cpu: [arm64] 491 + os: [win32] 492 + 493 + '@img/sharp-win32-ia32@0.34.3': 494 + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} 495 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 496 + cpu: [ia32] 497 + os: [win32] 498 + 499 + '@img/sharp-win32-x64@0.34.3': 500 + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} 501 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 502 + cpu: [x64] 503 + os: [win32] 504 + 505 + '@isaacs/balanced-match@4.0.1': 506 + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} 507 + engines: {node: 20 || >=22} 508 + 509 + '@isaacs/brace-expansion@5.0.0': 510 + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} 511 + engines: {node: 20 || >=22} 512 + 513 + '@isaacs/cliui@8.0.2': 514 + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 515 + engines: {node: '>=12'} 516 + 517 + '@jridgewell/gen-mapping@0.3.13': 518 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 519 + 520 + '@jridgewell/remapping@2.3.5': 521 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 522 + 523 + '@jridgewell/resolve-uri@3.1.2': 524 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 525 + engines: {node: '>=6.0.0'} 526 + 527 + '@jridgewell/sourcemap-codec@1.5.5': 528 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 529 + 530 + '@jridgewell/trace-mapping@0.3.30': 531 + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} 532 + 533 + '@jridgewell/trace-mapping@0.3.31': 534 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 535 + 536 + '@nodelib/fs.scandir@2.1.5': 537 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 538 + engines: {node: '>= 8'} 539 + 540 + '@nodelib/fs.stat@2.0.5': 541 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 542 + engines: {node: '>= 8'} 543 + 544 + '@nodelib/fs.walk@1.2.8': 545 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 546 + engines: {node: '>= 8'} 547 + 548 + '@pkgjs/parseargs@0.11.0': 549 + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 550 + engines: {node: '>=14'} 551 + 552 + '@pnpm/config.env-replace@1.1.0': 553 + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} 554 + engines: {node: '>=12.22.0'} 555 + 556 + '@pnpm/network.ca-file@1.0.2': 557 + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} 558 + engines: {node: '>=12.22.0'} 559 + 560 + '@pnpm/npm-conf@2.3.1': 561 + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} 562 + engines: {node: '>=12'} 563 + 564 + '@rolldown/pluginutils@1.0.0-beta.34': 565 + resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} 566 + 567 + '@rollup/rollup-android-arm-eabi@4.50.1': 568 + resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} 569 + cpu: [arm] 570 + os: [android] 571 + 572 + '@rollup/rollup-android-arm64@4.50.1': 573 + resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} 574 + cpu: [arm64] 575 + os: [android] 576 + 577 + '@rollup/rollup-darwin-arm64@4.50.1': 578 + resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} 579 + cpu: [arm64] 580 + os: [darwin] 581 + 582 + '@rollup/rollup-darwin-x64@4.50.1': 583 + resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} 584 + cpu: [x64] 585 + os: [darwin] 586 + 587 + '@rollup/rollup-freebsd-arm64@4.50.1': 588 + resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} 589 + cpu: [arm64] 590 + os: [freebsd] 591 + 592 + '@rollup/rollup-freebsd-x64@4.50.1': 593 + resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} 594 + cpu: [x64] 595 + os: [freebsd] 596 + 597 + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': 598 + resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} 599 + cpu: [arm] 600 + os: [linux] 601 + 602 + '@rollup/rollup-linux-arm-musleabihf@4.50.1': 603 + resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} 604 + cpu: [arm] 605 + os: [linux] 606 + 607 + '@rollup/rollup-linux-arm64-gnu@4.50.1': 608 + resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} 609 + cpu: [arm64] 610 + os: [linux] 611 + 612 + '@rollup/rollup-linux-arm64-musl@4.50.1': 613 + resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} 614 + cpu: [arm64] 615 + os: [linux] 616 + 617 + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': 618 + resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} 619 + cpu: [loong64] 620 + os: [linux] 621 + 622 + '@rollup/rollup-linux-ppc64-gnu@4.50.1': 623 + resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} 624 + cpu: [ppc64] 625 + os: [linux] 626 + 627 + '@rollup/rollup-linux-riscv64-gnu@4.50.1': 628 + resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} 629 + cpu: [riscv64] 630 + os: [linux] 631 + 632 + '@rollup/rollup-linux-riscv64-musl@4.50.1': 633 + resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} 634 + cpu: [riscv64] 635 + os: [linux] 636 + 637 + '@rollup/rollup-linux-s390x-gnu@4.50.1': 638 + resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} 639 + cpu: [s390x] 640 + os: [linux] 641 + 642 + '@rollup/rollup-linux-x64-gnu@4.50.1': 643 + resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} 644 + cpu: [x64] 645 + os: [linux] 646 + 647 + '@rollup/rollup-linux-x64-musl@4.50.1': 648 + resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} 649 + cpu: [x64] 650 + os: [linux] 651 + 652 + '@rollup/rollup-openharmony-arm64@4.50.1': 653 + resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} 654 + cpu: [arm64] 655 + os: [openharmony] 656 + 657 + '@rollup/rollup-win32-arm64-msvc@4.50.1': 658 + resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} 659 + cpu: [arm64] 660 + os: [win32] 661 + 662 + '@rollup/rollup-win32-ia32-msvc@4.50.1': 663 + resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} 664 + cpu: [ia32] 665 + os: [win32] 666 + 667 + '@rollup/rollup-win32-x64-msvc@4.50.1': 668 + resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} 669 + cpu: [x64] 670 + os: [win32] 671 + 672 + '@types/babel__core@7.20.5': 673 + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 674 + 675 + '@types/babel__generator@7.27.0': 676 + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} 677 + 678 + '@types/babel__template@7.4.4': 679 + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 680 + 681 + '@types/babel__traverse@7.28.0': 682 + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} 683 + 684 + '@types/estree@1.0.8': 685 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 686 + 687 + '@types/filesystem@0.0.36': 688 + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} 689 + 690 + '@types/filewriter@0.0.33': 691 + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} 692 + 693 + '@types/har-format@1.2.16': 694 + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} 695 + 696 + '@types/minimatch@3.0.5': 697 + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} 698 + 699 + '@types/node@24.3.2': 700 + resolution: {integrity: sha512-6L8PkB+m1SSb2kaGGFk3iXENxl8lrs7cyVl7AXH6pgdMfulDfM6yUrVdjtxdnGrLrGzzuav8fFnZMY+rcscqcA==} 701 + 702 + '@types/react-dom@19.1.9': 703 + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} 704 + peerDependencies: 705 + '@types/react': ^19.0.0 706 + 707 + '@types/react@19.1.13': 708 + resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==} 709 + 710 + '@types/yauzl@2.10.3': 711 + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} 712 + 713 + '@vitejs/plugin-react@5.0.2': 714 + resolution: {integrity: sha512-tmyFgixPZCx2+e6VO9TNITWcCQl8+Nl/E8YbAyPVv85QCc7/A3JrdfG2A8gIzvVhWuzMOVrFW1aReaNxrI6tbw==} 715 + engines: {node: ^20.19.0 || >=22.12.0} 716 + peerDependencies: 717 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 718 + 719 + '@webext-core/fake-browser@1.3.2': 720 + resolution: {integrity: sha512-jFyPWWz+VkHAC9DRIiIPOyu6X/KlC8dYqSKweHz6tsDb86QawtVgZSpYcM+GOQBlZc5DHFo92jJ7cIq4uBnU0A==} 721 + 722 + '@webext-core/isolated-element@1.1.2': 723 + resolution: {integrity: sha512-CNHYhsIR8TPkPb+4yqTIuzaGnVn/Fshev5fyoPW+/8Cyc93tJbCjP9PC1XSK6fDWu+xASdPHLZaoa2nWAYoxeQ==} 724 + 725 + '@webext-core/match-patterns@1.0.3': 726 + resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} 727 + 728 + '@wxt-dev/auto-icons@1.1.0': 729 + resolution: {integrity: sha512-lDFZjDbrY5gDaapUuUOYTPudE88oB3Z7rTdg0N7iq2WIWga1h0bhzCJDaqNqMvPN2DCYvHFfA0cnqA12vEJjiA==} 730 + peerDependencies: 731 + wxt: '>=0.19.0' 732 + 733 + '@wxt-dev/browser@0.1.4': 734 + resolution: {integrity: sha512-9x03I15i79XU8qYwjv4le0K2HdMl/Yga2wUBSoUbcrCnamv8P3nvuYxREQ9C5QY/qPAfeEVdAtaTrS3KWak71g==} 735 + 736 + '@wxt-dev/module-react@1.1.5': 737 + resolution: {integrity: sha512-KgsUrsgH5rBT8MwiipnDEOHBXmLvTIdFICrI7KjngqSf9DpVRn92HsKmToxY0AYpkP19hHWta2oNYFTzmmm++g==} 738 + peerDependencies: 739 + wxt: '>=0.19.16' 740 + 741 + '@wxt-dev/storage@1.2.0': 742 + resolution: {integrity: sha512-4A44zCpwl5GZdmUdSJvUWJ6ekZZ+Fz5ttYqTGPIRJSsyosKX8X8Yl7D2Loy1ZlqIg6oJHysaiFXALtTE+pFjpw==} 743 + 744 + acorn@8.15.0: 745 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 746 + engines: {node: '>=0.4.0'} 747 + hasBin: true 748 + 749 + adm-zip@0.5.16: 750 + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} 751 + engines: {node: '>=12.0'} 752 + 753 + ansi-align@3.0.1: 754 + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} 755 + 756 + ansi-escapes@7.1.0: 757 + resolution: {integrity: sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g==} 758 + engines: {node: '>=18'} 759 + 760 + ansi-regex@5.0.1: 761 + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 762 + engines: {node: '>=8'} 763 + 764 + ansi-regex@6.2.0: 765 + resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} 766 + engines: {node: '>=12'} 767 + 768 + ansi-regex@6.2.2: 769 + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} 770 + engines: {node: '>=12'} 771 + 772 + ansi-styles@4.3.0: 773 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 774 + engines: {node: '>=8'} 775 + 776 + ansi-styles@6.2.1: 777 + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 778 + engines: {node: '>=12'} 779 + 780 + ansi-styles@6.2.3: 781 + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} 782 + engines: {node: '>=12'} 783 + 784 + any-promise@1.3.0: 785 + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 786 + 787 + anymatch@3.1.3: 788 + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 789 + engines: {node: '>= 8'} 790 + 791 + arg@5.0.2: 792 + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 793 + 794 + array-differ@4.0.0: 795 + resolution: {integrity: sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==} 796 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 797 + 798 + array-union@3.0.1: 799 + resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} 800 + engines: {node: '>=12'} 801 + 802 + async-mutex@0.5.0: 803 + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} 804 + 805 + async@3.2.6: 806 + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} 807 + 808 + atomic-sleep@1.0.0: 809 + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 810 + engines: {node: '>=8.0.0'} 811 + 812 + atomically@2.0.3: 813 + resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} 814 + 815 + autoprefixer@10.4.21: 816 + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} 817 + engines: {node: ^10 || ^12 || >=14} 818 + hasBin: true 819 + peerDependencies: 820 + postcss: ^8.1.0 821 + 822 + balanced-match@1.0.2: 823 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 824 + 825 + base64-js@1.5.1: 826 + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 827 + 828 + baseline-browser-mapping@2.8.2: 829 + resolution: {integrity: sha512-NvcIedLxrs9llVpX7wI+Jz4Hn9vJQkCPKrTaHIE0sW/Rj1iq6Fzby4NbyTZjQJNoypBXNaG7tEHkTgONZpwgxQ==} 830 + hasBin: true 831 + 832 + binary-extensions@2.3.0: 833 + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 834 + engines: {node: '>=8'} 835 + 836 + bl@5.1.0: 837 + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} 838 + 839 + bluebird@3.7.2: 840 + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} 841 + 842 + boolbase@1.0.0: 843 + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} 844 + 845 + boxen@8.0.1: 846 + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} 847 + engines: {node: '>=18'} 848 + 849 + brace-expansion@1.1.12: 850 + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} 851 + 852 + brace-expansion@2.0.2: 853 + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 854 + 855 + braces@3.0.3: 856 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 857 + engines: {node: '>=8'} 858 + 859 + browserslist@4.25.3: 860 + resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} 861 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 862 + hasBin: true 863 + 864 + browserslist@4.26.0: 865 + resolution: {integrity: sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==} 866 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 867 + hasBin: true 868 + 869 + buffer-crc32@0.2.13: 870 + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} 871 + 872 + buffer-from@1.1.2: 873 + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 874 + 875 + buffer@6.0.3: 876 + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 877 + 878 + bundle-name@4.1.0: 879 + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} 880 + engines: {node: '>=18'} 881 + 882 + c12@3.2.0: 883 + resolution: {integrity: sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ==} 884 + peerDependencies: 885 + magicast: ^0.3.5 886 + peerDependenciesMeta: 887 + magicast: 888 + optional: true 889 + 890 + cac@6.7.14: 891 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 892 + engines: {node: '>=8'} 893 + 894 + camelcase-css@2.0.1: 895 + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 896 + engines: {node: '>= 6'} 897 + 898 + camelcase@8.0.0: 899 + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} 900 + engines: {node: '>=16'} 901 + 902 + caniuse-lite@1.0.30001735: 903 + resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} 904 + 905 + caniuse-lite@1.0.30001741: 906 + resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} 907 + 908 + chalk@4.1.2: 909 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 910 + engines: {node: '>=10'} 911 + 912 + chalk@5.6.2: 913 + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} 914 + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 915 + 916 + chokidar@3.6.0: 917 + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 918 + engines: {node: '>= 8.10.0'} 919 + 920 + chokidar@4.0.3: 921 + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 922 + engines: {node: '>= 14.16.0'} 923 + 924 + chrome-launcher@1.2.0: 925 + resolution: {integrity: sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==} 926 + engines: {node: '>=12.13.0'} 927 + hasBin: true 928 + 929 + ci-info@4.3.0: 930 + resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} 931 + engines: {node: '>=8'} 932 + 933 + citty@0.1.6: 934 + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} 935 + 936 + cli-boxes@3.0.0: 937 + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} 938 + engines: {node: '>=10'} 939 + 940 + cli-cursor@4.0.0: 941 + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} 942 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 943 + 944 + cli-cursor@5.0.0: 945 + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} 946 + engines: {node: '>=18'} 947 + 948 + cli-highlight@2.1.11: 949 + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} 950 + engines: {node: '>=8.0.0', npm: '>=5.0.0'} 951 + hasBin: true 952 + 953 + cli-spinners@2.9.2: 954 + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} 955 + engines: {node: '>=6'} 956 + 957 + cli-truncate@4.0.0: 958 + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} 959 + engines: {node: '>=18'} 960 + 961 + cliui@7.0.4: 962 + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} 963 + 964 + cliui@8.0.1: 965 + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 966 + engines: {node: '>=12'} 967 + 968 + clone@1.0.4: 969 + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} 970 + engines: {node: '>=0.8'} 971 + 972 + color-convert@2.0.1: 973 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 974 + engines: {node: '>=7.0.0'} 975 + 976 + color-name@1.1.4: 977 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 978 + 979 + color-string@1.9.1: 980 + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 981 + 982 + color@4.2.3: 983 + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 984 + engines: {node: '>=12.5.0'} 985 + 986 + colorette@2.0.20: 987 + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 988 + 989 + commander@2.9.0: 990 + resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==} 991 + engines: {node: '>= 0.6.x'} 992 + 993 + commander@4.1.1: 994 + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 995 + engines: {node: '>= 6'} 996 + 997 + commander@9.5.0: 998 + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} 999 + engines: {node: ^12.20.0 || >=14} 1000 + 1001 + concat-map@0.0.1: 1002 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 1003 + 1004 + concat-stream@1.6.2: 1005 + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} 1006 + engines: {'0': node >= 0.8} 1007 + 1008 + confbox@0.1.8: 1009 + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 1010 + 1011 + confbox@0.2.2: 1012 + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} 1013 + 1014 + config-chain@1.1.13: 1015 + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} 1016 + 1017 + configstore@7.0.0: 1018 + resolution: {integrity: sha512-yk7/5PN5im4qwz0WFZW3PXnzHgPu9mX29Y8uZ3aefe2lBPC1FYttWZRcaW9fKkT0pBCJyuQ2HfbmPVaODi9jcQ==} 1019 + engines: {node: '>=18'} 1020 + 1021 + consola@3.4.2: 1022 + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 1023 + engines: {node: ^14.18.0 || >=16.10.0} 1024 + 1025 + convert-source-map@2.0.0: 1026 + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 1027 + 1028 + core-util-is@1.0.3: 1029 + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} 1030 + 1031 + cross-spawn@7.0.6: 1032 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 1033 + engines: {node: '>= 8'} 1034 + 1035 + css-select@5.2.2: 1036 + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} 1037 + 1038 + css-what@6.2.2: 1039 + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} 1040 + engines: {node: '>= 6'} 1041 + 1042 + cssesc@3.0.0: 1043 + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1044 + engines: {node: '>=4'} 1045 + hasBin: true 1046 + 1047 + cssom@0.5.0: 1048 + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} 1049 + 1050 + csstype@3.1.3: 1051 + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 1052 + 1053 + debounce@1.2.1: 1054 + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} 1055 + 1056 + debug@4.3.7: 1057 + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 1058 + engines: {node: '>=6.0'} 1059 + peerDependencies: 1060 + supports-color: '*' 1061 + peerDependenciesMeta: 1062 + supports-color: 1063 + optional: true 1064 + 1065 + debug@4.4.1: 1066 + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 1067 + engines: {node: '>=6.0'} 1068 + peerDependencies: 1069 + supports-color: '*' 1070 + peerDependenciesMeta: 1071 + supports-color: 1072 + optional: true 1073 + 1074 + deep-extend@0.6.0: 1075 + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 1076 + engines: {node: '>=4.0.0'} 1077 + 1078 + default-browser-id@5.0.0: 1079 + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} 1080 + engines: {node: '>=18'} 1081 + 1082 + default-browser@5.2.1: 1083 + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} 1084 + engines: {node: '>=18'} 1085 + 1086 + defaults@1.0.4: 1087 + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} 1088 + 1089 + define-lazy-prop@2.0.0: 1090 + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} 1091 + engines: {node: '>=8'} 1092 + 1093 + define-lazy-prop@3.0.0: 1094 + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 1095 + engines: {node: '>=12'} 1096 + 1097 + defu@6.1.4: 1098 + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 1099 + 1100 + dequal@2.0.3: 1101 + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} 1102 + engines: {node: '>=6'} 1103 + 1104 + destr@2.0.5: 1105 + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} 1106 + 1107 + detect-libc@2.0.4: 1108 + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} 1109 + engines: {node: '>=8'} 1110 + 1111 + didyoumean@1.2.2: 1112 + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 1113 + 1114 + dlv@1.1.3: 1115 + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 1116 + 1117 + dom-serializer@2.0.0: 1118 + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} 1119 + 1120 + domelementtype@2.3.0: 1121 + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} 1122 + 1123 + domhandler@5.0.3: 1124 + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} 1125 + engines: {node: '>= 4'} 1126 + 1127 + domutils@3.2.2: 1128 + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 1129 + 1130 + dot-prop@9.0.0: 1131 + resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} 1132 + engines: {node: '>=18'} 1133 + 1134 + dotenv-expand@12.0.3: 1135 + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} 1136 + engines: {node: '>=12'} 1137 + 1138 + dotenv@16.6.1: 1139 + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} 1140 + engines: {node: '>=12'} 1141 + 1142 + dotenv@17.2.2: 1143 + resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} 1144 + engines: {node: '>=12'} 1145 + 1146 + eastasianwidth@0.2.0: 1147 + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 1148 + 1149 + electron-to-chromium@1.5.207: 1150 + resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} 1151 + 1152 + electron-to-chromium@1.5.218: 1153 + resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==} 1154 + 1155 + emoji-regex@10.5.0: 1156 + resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} 1157 + 1158 + emoji-regex@8.0.0: 1159 + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 1160 + 1161 + emoji-regex@9.2.2: 1162 + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 1163 + 1164 + end-of-stream@1.4.5: 1165 + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} 1166 + 1167 + entities@4.5.0: 1168 + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 1169 + engines: {node: '>=0.12'} 1170 + 1171 + entities@6.0.1: 1172 + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} 1173 + engines: {node: '>=0.12'} 1174 + 1175 + environment@1.1.0: 1176 + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} 1177 + engines: {node: '>=18'} 1178 + 1179 + error-ex@1.3.2: 1180 + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} 1181 + 1182 + es-module-lexer@1.7.0: 1183 + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 1184 + 1185 + es6-error@4.1.1: 1186 + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} 1187 + 1188 + esbuild@0.25.9: 1189 + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} 1190 + engines: {node: '>=18'} 1191 + hasBin: true 1192 + 1193 + escalade@3.2.0: 1194 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 1195 + engines: {node: '>=6'} 1196 + 1197 + escape-goat@4.0.0: 1198 + resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} 1199 + engines: {node: '>=12'} 1200 + 1201 + escape-string-regexp@4.0.0: 1202 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 1203 + engines: {node: '>=10'} 1204 + 1205 + escape-string-regexp@5.0.0: 1206 + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 1207 + engines: {node: '>=12'} 1208 + 1209 + estree-walker@3.0.3: 1210 + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 1211 + 1212 + eventemitter3@5.0.1: 1213 + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} 1214 + 1215 + exsolve@1.0.7: 1216 + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} 1217 + 1218 + extract-zip@2.0.1: 1219 + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} 1220 + engines: {node: '>= 10.17.0'} 1221 + hasBin: true 1222 + 1223 + fast-glob@3.3.3: 1224 + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 1225 + engines: {node: '>=8.6.0'} 1226 + 1227 + fast-redact@3.5.0: 1228 + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} 1229 + engines: {node: '>=6'} 1230 + 1231 + fastq@1.19.1: 1232 + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 1233 + 1234 + fd-slicer@1.1.0: 1235 + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} 1236 + 1237 + fdir@6.5.0: 1238 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1239 + engines: {node: '>=12.0.0'} 1240 + peerDependencies: 1241 + picomatch: ^3 || ^4 1242 + peerDependenciesMeta: 1243 + picomatch: 1244 + optional: true 1245 + 1246 + filesize@11.0.2: 1247 + resolution: {integrity: sha512-s/iAeeWLk5BschUIpmdrF8RA8lhFZ/xDZgKw1Tan72oGws1/dFGB06nYEiyyssWUfjKNQTNRlrwMVjO9/hvXDw==} 1248 + engines: {node: '>= 10.4.0'} 1249 + 1250 + fill-range@7.1.1: 1251 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 1252 + engines: {node: '>=8'} 1253 + 1254 + firefox-profile@4.7.0: 1255 + resolution: {integrity: sha512-aGApEu5bfCNbA4PGUZiRJAIU6jKmghV2UVdklXAofnNtiDjqYw0czLS46W7IfFqVKgKhFB8Ao2YoNGHY4BoIMQ==} 1256 + engines: {node: '>=18'} 1257 + hasBin: true 1258 + 1259 + foreground-child@3.3.1: 1260 + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 1261 + engines: {node: '>=14'} 1262 + 1263 + formdata-node@6.0.3: 1264 + resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} 1265 + engines: {node: '>= 18'} 1266 + 1267 + fraction.js@4.3.7: 1268 + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 1269 + 1270 + fs-extra@11.3.1: 1271 + resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} 1272 + engines: {node: '>=14.14'} 1273 + 1274 + fsevents@2.3.3: 1275 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 1276 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1277 + os: [darwin] 1278 + 1279 + function-bind@1.1.2: 1280 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 1281 + 1282 + fx-runner@1.4.0: 1283 + resolution: {integrity: sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==} 1284 + hasBin: true 1285 + 1286 + gensync@1.0.0-beta.2: 1287 + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1288 + engines: {node: '>=6.9.0'} 1289 + 1290 + get-caller-file@2.0.5: 1291 + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 1292 + engines: {node: 6.* || 8.* || >= 10.*} 1293 + 1294 + get-east-asian-width@1.4.0: 1295 + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} 1296 + engines: {node: '>=18'} 1297 + 1298 + get-port-please@3.2.0: 1299 + resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} 1300 + 1301 + get-stream@5.2.0: 1302 + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} 1303 + engines: {node: '>=8'} 1304 + 1305 + giget@2.0.0: 1306 + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} 1307 + hasBin: true 1308 + 1309 + glob-parent@5.1.2: 1310 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1311 + engines: {node: '>= 6'} 1312 + 1313 + glob-parent@6.0.2: 1314 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 1315 + engines: {node: '>=10.13.0'} 1316 + 1317 + glob-to-regexp@0.4.1: 1318 + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 1319 + 1320 + glob@10.4.5: 1321 + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 1322 + hasBin: true 1323 + 1324 + global-directory@4.0.1: 1325 + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} 1326 + engines: {node: '>=18'} 1327 + 1328 + graceful-fs@4.2.10: 1329 + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} 1330 + 1331 + graceful-fs@4.2.11: 1332 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 1333 + 1334 + graceful-readlink@1.0.1: 1335 + resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} 1336 + 1337 + growly@1.3.0: 1338 + resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} 1339 + 1340 + has-flag@4.0.0: 1341 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1342 + engines: {node: '>=8'} 1343 + 1344 + hasown@2.0.2: 1345 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 1346 + engines: {node: '>= 0.4'} 1347 + 1348 + highlight.js@10.7.3: 1349 + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} 1350 + 1351 + hookable@5.5.3: 1352 + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} 1353 + 1354 + html-escaper@3.0.3: 1355 + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} 1356 + 1357 + htmlparser2@10.0.0: 1358 + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} 1359 + 1360 + ieee754@1.2.1: 1361 + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 1362 + 1363 + immediate@3.0.6: 1364 + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} 1365 + 1366 + import-meta-resolve@4.2.0: 1367 + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} 1368 + 1369 + inherits@2.0.4: 1370 + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 1371 + 1372 + ini@1.3.8: 1373 + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 1374 + 1375 + ini@4.1.1: 1376 + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} 1377 + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 1378 + 1379 + ini@4.1.3: 1380 + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} 1381 + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 1382 + 1383 + is-absolute@0.1.7: 1384 + resolution: {integrity: sha512-Xi9/ZSn4NFapG8RP98iNPMOeaV3mXPisxKxzKtHVqr3g56j/fBn+yZmnxSVAA8lmZbl2J9b/a4kJvfU3hqQYgA==} 1385 + engines: {node: '>=0.10.0'} 1386 + 1387 + is-arrayish@0.2.1: 1388 + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} 1389 + 1390 + is-arrayish@0.3.2: 1391 + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 1392 + 1393 + is-binary-path@2.1.0: 1394 + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 1395 + engines: {node: '>=8'} 1396 + 1397 + is-core-module@2.16.1: 1398 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1399 + engines: {node: '>= 0.4'} 1400 + 1401 + is-docker@2.2.1: 1402 + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 1403 + engines: {node: '>=8'} 1404 + hasBin: true 1405 + 1406 + is-docker@3.0.0: 1407 + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 1408 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1409 + hasBin: true 1410 + 1411 + is-extglob@2.1.1: 1412 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1413 + engines: {node: '>=0.10.0'} 1414 + 1415 + is-fullwidth-code-point@3.0.0: 1416 + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1417 + engines: {node: '>=8'} 1418 + 1419 + is-fullwidth-code-point@4.0.0: 1420 + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} 1421 + engines: {node: '>=12'} 1422 + 1423 + is-fullwidth-code-point@5.1.0: 1424 + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} 1425 + engines: {node: '>=18'} 1426 + 1427 + is-glob@4.0.3: 1428 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1429 + engines: {node: '>=0.10.0'} 1430 + 1431 + is-in-ci@1.0.0: 1432 + resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} 1433 + engines: {node: '>=18'} 1434 + hasBin: true 1435 + 1436 + is-inside-container@1.0.0: 1437 + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 1438 + engines: {node: '>=14.16'} 1439 + hasBin: true 1440 + 1441 + is-installed-globally@1.0.0: 1442 + resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} 1443 + engines: {node: '>=18'} 1444 + 1445 + is-interactive@2.0.0: 1446 + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} 1447 + engines: {node: '>=12'} 1448 + 1449 + is-npm@6.1.0: 1450 + resolution: {integrity: sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==} 1451 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1452 + 1453 + is-number@7.0.0: 1454 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1455 + engines: {node: '>=0.12.0'} 1456 + 1457 + is-path-inside@4.0.0: 1458 + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} 1459 + engines: {node: '>=12'} 1460 + 1461 + is-plain-object@2.0.4: 1462 + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} 1463 + engines: {node: '>=0.10.0'} 1464 + 1465 + is-potential-custom-element-name@1.0.1: 1466 + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} 1467 + 1468 + is-primitive@3.0.1: 1469 + resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} 1470 + engines: {node: '>=0.10.0'} 1471 + 1472 + is-relative@0.1.3: 1473 + resolution: {integrity: sha512-wBOr+rNM4gkAZqoLRJI4myw5WzzIdQosFAAbnvfXP5z1LyzgAI3ivOKehC5KfqlQJZoihVhirgtCBj378Eg8GA==} 1474 + engines: {node: '>=0.10.0'} 1475 + 1476 + is-unicode-supported@1.3.0: 1477 + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} 1478 + engines: {node: '>=12'} 1479 + 1480 + is-unicode-supported@2.1.0: 1481 + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} 1482 + engines: {node: '>=18'} 1483 + 1484 + is-wsl@2.2.0: 1485 + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 1486 + engines: {node: '>=8'} 1487 + 1488 + is-wsl@3.1.0: 1489 + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} 1490 + engines: {node: '>=16'} 1491 + 1492 + isarray@1.0.0: 1493 + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} 1494 + 1495 + isexe@1.1.2: 1496 + resolution: {integrity: sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw==} 1497 + 1498 + isexe@2.0.0: 1499 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1500 + 1501 + isobject@3.0.1: 1502 + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} 1503 + engines: {node: '>=0.10.0'} 1504 + 1505 + jackspeak@3.4.3: 1506 + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 1507 + 1508 + jiti@1.21.7: 1509 + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} 1510 + hasBin: true 1511 + 1512 + jiti@2.5.1: 1513 + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} 1514 + hasBin: true 1515 + 1516 + js-tokens@4.0.0: 1517 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1518 + 1519 + js-tokens@9.0.1: 1520 + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} 1521 + 1522 + jsesc@3.1.0: 1523 + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 1524 + engines: {node: '>=6'} 1525 + hasBin: true 1526 + 1527 + json-parse-even-better-errors@3.0.2: 1528 + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} 1529 + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 1530 + 1531 + json5@2.2.3: 1532 + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 1533 + engines: {node: '>=6'} 1534 + hasBin: true 1535 + 1536 + jsonfile@6.2.0: 1537 + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} 1538 + 1539 + jszip@3.10.1: 1540 + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} 1541 + 1542 + kleur@3.0.3: 1543 + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 1544 + engines: {node: '>=6'} 1545 + 1546 + ky@1.10.0: 1547 + resolution: {integrity: sha512-YRPCzHEWZffbfvmRrfwa+5nwBHwZuYiTrfDX0wuhGBPV0pA/zCqcOq93MDssON/baIkpYbvehIX5aLpMxrRhaA==} 1548 + engines: {node: '>=18'} 1549 + 1550 + latest-version@9.0.0: 1551 + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} 1552 + engines: {node: '>=18'} 1553 + 1554 + lie@3.3.0: 1555 + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} 1556 + 1557 + lighthouse-logger@2.0.2: 1558 + resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} 1559 + 1560 + lilconfig@2.1.0: 1561 + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 1562 + engines: {node: '>=10'} 1563 + 1564 + lilconfig@3.1.3: 1565 + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 1566 + engines: {node: '>=14'} 1567 + 1568 + lines-and-columns@1.2.4: 1569 + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 1570 + 1571 + lines-and-columns@2.0.4: 1572 + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} 1573 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1574 + 1575 + linkedom@0.18.12: 1576 + resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==} 1577 + engines: {node: '>=16'} 1578 + peerDependencies: 1579 + canvas: '>= 2' 1580 + peerDependenciesMeta: 1581 + canvas: 1582 + optional: true 1583 + 1584 + listr2@8.3.3: 1585 + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} 1586 + engines: {node: '>=18.0.0'} 1587 + 1588 + local-pkg@1.1.2: 1589 + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} 1590 + engines: {node: '>=14'} 1591 + 1592 + lodash.camelcase@4.3.0: 1593 + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} 1594 + 1595 + lodash.kebabcase@4.1.1: 1596 + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} 1597 + 1598 + lodash.merge@4.6.2: 1599 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1600 + 1601 + lodash.snakecase@4.1.1: 1602 + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} 1603 + 1604 + log-symbols@5.1.0: 1605 + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} 1606 + engines: {node: '>=12'} 1607 + 1608 + log-symbols@6.0.0: 1609 + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} 1610 + engines: {node: '>=18'} 1611 + 1612 + log-update@6.1.0: 1613 + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} 1614 + engines: {node: '>=18'} 1615 + 1616 + lru-cache@10.4.3: 1617 + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 1618 + 1619 + lru-cache@5.1.1: 1620 + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 1621 + 1622 + magic-string@0.30.19: 1623 + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} 1624 + 1625 + magicast@0.3.5: 1626 + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} 1627 + 1628 + make-error@1.3.6: 1629 + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 1630 + 1631 + many-keys-map@2.0.1: 1632 + resolution: {integrity: sha512-DHnZAD4phTbZ+qnJdjoNEVU1NecYoSdbOOoVmTDH46AuxDkEVh3MxTVpXq10GtcTC6mndN9dkv1rNfpjRcLnOw==} 1633 + 1634 + marky@1.3.0: 1635 + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} 1636 + 1637 + merge2@1.4.1: 1638 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1639 + engines: {node: '>= 8'} 1640 + 1641 + micromatch@4.0.8: 1642 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 1643 + engines: {node: '>=8.6'} 1644 + 1645 + mimic-fn@2.1.0: 1646 + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 1647 + engines: {node: '>=6'} 1648 + 1649 + mimic-function@5.0.1: 1650 + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 1651 + engines: {node: '>=18'} 1652 + 1653 + minimatch@10.0.3: 1654 + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} 1655 + engines: {node: 20 || >=22} 1656 + 1657 + minimatch@3.1.2: 1658 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1659 + 1660 + minimatch@9.0.5: 1661 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1662 + engines: {node: '>=16 || 14 >=14.17'} 1663 + 1664 + minimist@1.2.8: 1665 + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1666 + 1667 + minipass@7.1.2: 1668 + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 1669 + engines: {node: '>=16 || 14 >=14.17'} 1670 + 1671 + mlly@1.8.0: 1672 + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} 1673 + 1674 + ms@2.1.3: 1675 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1676 + 1677 + multimatch@6.0.0: 1678 + resolution: {integrity: sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ==} 1679 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1680 + 1681 + mz@2.7.0: 1682 + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 1683 + 1684 + nano-spawn@1.0.3: 1685 + resolution: {integrity: sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA==} 1686 + engines: {node: '>=20.17'} 1687 + 1688 + nanoid@3.3.11: 1689 + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 1690 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1691 + hasBin: true 1692 + 1693 + node-fetch-native@1.6.7: 1694 + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} 1695 + 1696 + node-forge@1.3.1: 1697 + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} 1698 + engines: {node: '>= 6.13.0'} 1699 + 1700 + node-notifier@10.0.1: 1701 + resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} 1702 + 1703 + node-releases@2.0.19: 1704 + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 1705 + 1706 + node-releases@2.0.21: 1707 + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} 1708 + 1709 + normalize-path@3.0.0: 1710 + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1711 + engines: {node: '>=0.10.0'} 1712 + 1713 + normalize-range@0.1.2: 1714 + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} 1715 + engines: {node: '>=0.10.0'} 1716 + 1717 + nth-check@2.1.1: 1718 + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} 1719 + 1720 + nypm@0.6.1: 1721 + resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} 1722 + engines: {node: ^14.16.0 || >=16.10.0} 1723 + hasBin: true 1724 + 1725 + object-assign@4.1.1: 1726 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1727 + engines: {node: '>=0.10.0'} 1728 + 1729 + object-hash@3.0.0: 1730 + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 1731 + engines: {node: '>= 6'} 1732 + 1733 + ofetch@1.4.1: 1734 + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 1735 + 1736 + ohash@2.0.11: 1737 + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} 1738 + 1739 + on-exit-leak-free@2.1.2: 1740 + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} 1741 + engines: {node: '>=14.0.0'} 1742 + 1743 + once@1.4.0: 1744 + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1745 + 1746 + onetime@5.1.2: 1747 + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1748 + engines: {node: '>=6'} 1749 + 1750 + onetime@7.0.0: 1751 + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 1752 + engines: {node: '>=18'} 1753 + 1754 + open@10.2.0: 1755 + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} 1756 + engines: {node: '>=18'} 1757 + 1758 + open@8.4.2: 1759 + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} 1760 + engines: {node: '>=12'} 1761 + 1762 + ora@6.3.1: 1763 + resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} 1764 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1765 + 1766 + ora@8.2.0: 1767 + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} 1768 + engines: {node: '>=18'} 1769 + 1770 + os-shim@0.1.3: 1771 + resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} 1772 + engines: {node: '>= 0.4.0'} 1773 + 1774 + package-json-from-dist@1.0.1: 1775 + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 1776 + 1777 + package-json@10.0.1: 1778 + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} 1779 + engines: {node: '>=18'} 1780 + 1781 + pako@1.0.11: 1782 + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} 1783 + 1784 + parse-json@7.1.1: 1785 + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} 1786 + engines: {node: '>=16'} 1787 + 1788 + parse5-htmlparser2-tree-adapter@6.0.1: 1789 + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} 1790 + 1791 + parse5@5.1.1: 1792 + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} 1793 + 1794 + parse5@6.0.1: 1795 + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} 1796 + 1797 + path-key@3.1.1: 1798 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1799 + engines: {node: '>=8'} 1800 + 1801 + path-parse@1.0.7: 1802 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1803 + 1804 + path-scurry@1.11.1: 1805 + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 1806 + engines: {node: '>=16 || 14 >=14.18'} 1807 + 1808 + pathe@2.0.3: 1809 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1810 + 1811 + pend@1.2.0: 1812 + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} 1813 + 1814 + perfect-debounce@1.0.0: 1815 + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} 1816 + 1817 + perfect-debounce@2.0.0: 1818 + resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} 1819 + 1820 + picocolors@1.1.1: 1821 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1822 + 1823 + picomatch@2.3.1: 1824 + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1825 + engines: {node: '>=8.6'} 1826 + 1827 + picomatch@4.0.3: 1828 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1829 + engines: {node: '>=12'} 1830 + 1831 + pify@2.3.0: 1832 + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 1833 + engines: {node: '>=0.10.0'} 1834 + 1835 + pino-abstract-transport@2.0.0: 1836 + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} 1837 + 1838 + pino-std-serializers@7.0.0: 1839 + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} 1840 + 1841 + pino@9.7.0: 1842 + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} 1843 + hasBin: true 1844 + 1845 + pirates@4.0.7: 1846 + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 1847 + engines: {node: '>= 6'} 1848 + 1849 + pkg-types@1.3.1: 1850 + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} 1851 + 1852 + pkg-types@2.3.0: 1853 + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} 1854 + 1855 + postcss-import@15.1.0: 1856 + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 1857 + engines: {node: '>=14.0.0'} 1858 + peerDependencies: 1859 + postcss: ^8.0.0 1860 + 1861 + postcss-js@4.0.1: 1862 + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 1863 + engines: {node: ^12 || ^14 || >= 16} 1864 + peerDependencies: 1865 + postcss: ^8.4.21 1866 + 1867 + postcss-load-config@4.0.2: 1868 + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} 1869 + engines: {node: '>= 14'} 1870 + peerDependencies: 1871 + postcss: '>=8.0.9' 1872 + ts-node: '>=9.0.0' 1873 + peerDependenciesMeta: 1874 + postcss: 1875 + optional: true 1876 + ts-node: 1877 + optional: true 1878 + 1879 + postcss-nested@6.2.0: 1880 + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 1881 + engines: {node: '>=12.0'} 1882 + peerDependencies: 1883 + postcss: ^8.2.14 1884 + 1885 + postcss-selector-parser@6.1.2: 1886 + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 1887 + engines: {node: '>=4'} 1888 + 1889 + postcss-value-parser@4.2.0: 1890 + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 1891 + 1892 + postcss@8.5.6: 1893 + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1894 + engines: {node: ^10 || ^12 || >=14} 1895 + 1896 + process-nextick-args@2.0.1: 1897 + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} 1898 + 1899 + process-warning@5.0.0: 1900 + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} 1901 + 1902 + promise-toolbox@0.21.0: 1903 + resolution: {integrity: sha512-NV8aTmpwrZv+Iys54sSFOBx3tuVaOBvvrft5PNppnxy9xpU/akHbaWIril22AB22zaPgrgwKdD0KsrM0ptUtpg==} 1904 + engines: {node: '>=6'} 1905 + 1906 + prompts@2.4.2: 1907 + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} 1908 + engines: {node: '>= 6'} 1909 + 1910 + proto-list@1.2.4: 1911 + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} 1912 + 1913 + publish-browser-extension@3.0.2: 1914 + resolution: {integrity: sha512-yZLPF/WyyaKYUHmurDcSMYpgZLqpUkx/4482bLpelHyRlyghjo3951pJXw/KunMnO6pdwWEZGr0AJnvlls2H8g==} 1915 + engines: {node: ^18.0.0 || >=20.0.0} 1916 + hasBin: true 1917 + 1918 + pump@3.0.3: 1919 + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} 1920 + 1921 + pupa@3.1.0: 1922 + resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} 1923 + engines: {node: '>=12.20'} 1924 + 1925 + quansync@0.2.11: 1926 + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} 1927 + 1928 + queue-microtask@1.2.3: 1929 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1930 + 1931 + quick-format-unescaped@4.0.4: 1932 + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} 1933 + 1934 + rc9@2.1.2: 1935 + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} 1936 + 1937 + rc@1.2.8: 1938 + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 1939 + hasBin: true 1940 + 1941 + react-dom@19.1.1: 1942 + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} 1943 + peerDependencies: 1944 + react: ^19.1.1 1945 + 1946 + react-icons@5.5.0: 1947 + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} 1948 + peerDependencies: 1949 + react: '*' 1950 + 1951 + react-refresh@0.17.0: 1952 + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} 1953 + engines: {node: '>=0.10.0'} 1954 + 1955 + react@19.1.1: 1956 + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} 1957 + engines: {node: '>=0.10.0'} 1958 + 1959 + read-cache@1.0.0: 1960 + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 1961 + 1962 + readable-stream@2.3.8: 1963 + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} 1964 + 1965 + readable-stream@3.6.2: 1966 + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1967 + engines: {node: '>= 6'} 1968 + 1969 + readdirp@3.6.0: 1970 + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1971 + engines: {node: '>=8.10.0'} 1972 + 1973 + readdirp@4.1.2: 1974 + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 1975 + engines: {node: '>= 14.18.0'} 1976 + 1977 + real-require@0.2.0: 1978 + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 1979 + engines: {node: '>= 12.13.0'} 1980 + 1981 + registry-auth-token@5.1.0: 1982 + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} 1983 + engines: {node: '>=14'} 1984 + 1985 + registry-url@6.0.1: 1986 + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} 1987 + engines: {node: '>=12'} 1988 + 1989 + require-directory@2.1.1: 1990 + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1991 + engines: {node: '>=0.10.0'} 1992 + 1993 + resolve@1.22.10: 1994 + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} 1995 + engines: {node: '>= 0.4'} 1996 + hasBin: true 1997 + 1998 + restore-cursor@4.0.0: 1999 + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} 2000 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2001 + 2002 + restore-cursor@5.1.0: 2003 + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 2004 + engines: {node: '>=18'} 2005 + 2006 + reusify@1.1.0: 2007 + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 2008 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 2009 + 2010 + rfdc@1.4.1: 2011 + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} 2012 + 2013 + rollup@4.50.1: 2014 + resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} 2015 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 2016 + hasBin: true 2017 + 2018 + run-applescript@7.1.0: 2019 + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} 2020 + engines: {node: '>=18'} 2021 + 2022 + run-parallel@1.2.0: 2023 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 2024 + 2025 + safe-buffer@5.1.2: 2026 + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 2027 + 2028 + safe-buffer@5.2.1: 2029 + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 2030 + 2031 + safe-stable-stringify@2.5.0: 2032 + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 2033 + engines: {node: '>=10'} 2034 + 2035 + sax@1.4.1: 2036 + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} 2037 + 2038 + scheduler@0.26.0: 2039 + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} 2040 + 2041 + scule@1.3.0: 2042 + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} 2043 + 2044 + semver@6.3.1: 2045 + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 2046 + hasBin: true 2047 + 2048 + semver@7.7.2: 2049 + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} 2050 + engines: {node: '>=10'} 2051 + hasBin: true 2052 + 2053 + set-value@4.1.0: 2054 + resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} 2055 + engines: {node: '>=11.0'} 2056 + 2057 + setimmediate@1.0.5: 2058 + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} 2059 + 2060 + sharp@0.34.3: 2061 + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} 2062 + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 2063 + 2064 + shebang-command@2.0.0: 2065 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 2066 + engines: {node: '>=8'} 2067 + 2068 + shebang-regex@3.0.0: 2069 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 2070 + engines: {node: '>=8'} 2071 + 2072 + shell-quote@1.7.3: 2073 + resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} 2074 + 2075 + shellwords@0.1.1: 2076 + resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} 2077 + 2078 + signal-exit@3.0.7: 2079 + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 2080 + 2081 + signal-exit@4.1.0: 2082 + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 2083 + engines: {node: '>=14'} 2084 + 2085 + simple-swizzle@0.2.2: 2086 + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 2087 + 2088 + sisteransi@1.0.5: 2089 + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 2090 + 2091 + slice-ansi@5.0.0: 2092 + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} 2093 + engines: {node: '>=12'} 2094 + 2095 + slice-ansi@7.1.2: 2096 + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} 2097 + engines: {node: '>=18'} 2098 + 2099 + sonic-boom@4.2.0: 2100 + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} 2101 + 2102 + source-map-js@1.2.1: 2103 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 2104 + engines: {node: '>=0.10.0'} 2105 + 2106 + source-map-support@0.5.21: 2107 + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 2108 + 2109 + source-map@0.6.1: 2110 + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 2111 + engines: {node: '>=0.10.0'} 2112 + 2113 + source-map@0.7.6: 2114 + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} 2115 + engines: {node: '>= 12'} 2116 + 2117 + spawn-sync@1.0.15: 2118 + resolution: {integrity: sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==} 2119 + 2120 + split2@4.2.0: 2121 + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 2122 + engines: {node: '>= 10.x'} 2123 + 2124 + split@1.0.1: 2125 + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} 2126 + 2127 + stdin-discarder@0.1.0: 2128 + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} 2129 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2130 + 2131 + stdin-discarder@0.2.2: 2132 + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} 2133 + engines: {node: '>=18'} 2134 + 2135 + string-width@4.2.3: 2136 + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 2137 + engines: {node: '>=8'} 2138 + 2139 + string-width@5.1.2: 2140 + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 2141 + engines: {node: '>=12'} 2142 + 2143 + string-width@7.2.0: 2144 + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} 2145 + engines: {node: '>=18'} 2146 + 2147 + string_decoder@1.1.1: 2148 + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} 2149 + 2150 + string_decoder@1.3.0: 2151 + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 2152 + 2153 + strip-ansi@6.0.1: 2154 + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 2155 + engines: {node: '>=8'} 2156 + 2157 + strip-ansi@7.1.0: 2158 + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 2159 + engines: {node: '>=12'} 2160 + 2161 + strip-ansi@7.1.2: 2162 + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} 2163 + engines: {node: '>=12'} 2164 + 2165 + strip-bom@5.0.0: 2166 + resolution: {integrity: sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==} 2167 + engines: {node: '>=12'} 2168 + 2169 + strip-json-comments@2.0.1: 2170 + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 2171 + engines: {node: '>=0.10.0'} 2172 + 2173 + strip-json-comments@5.0.2: 2174 + resolution: {integrity: sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==} 2175 + engines: {node: '>=14.16'} 2176 + 2177 + strip-literal@3.0.0: 2178 + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} 2179 + 2180 + stubborn-fs@1.2.5: 2181 + resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} 2182 + 2183 + sucrase@3.35.0: 2184 + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 2185 + engines: {node: '>=16 || 14 >=14.17'} 2186 + hasBin: true 2187 + 2188 + supports-color@7.2.0: 2189 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 2190 + engines: {node: '>=8'} 2191 + 2192 + supports-preserve-symlinks-flag@1.0.0: 2193 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 2194 + engines: {node: '>= 0.4'} 2195 + 2196 + tailwindcss@3.4.1: 2197 + resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==} 2198 + engines: {node: '>=14.0.0'} 2199 + hasBin: true 2200 + 2201 + thenify-all@1.6.0: 2202 + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 2203 + engines: {node: '>=0.8'} 2204 + 2205 + thenify@3.3.1: 2206 + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 2207 + 2208 + thread-stream@3.1.0: 2209 + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} 2210 + 2211 + through@2.3.8: 2212 + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} 2213 + 2214 + tinyexec@1.0.1: 2215 + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} 2216 + 2217 + tinyglobby@0.2.15: 2218 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 2219 + engines: {node: '>=12.0.0'} 2220 + 2221 + tmp@0.2.5: 2222 + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} 2223 + engines: {node: '>=14.14'} 2224 + 2225 + to-regex-range@5.0.1: 2226 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 2227 + engines: {node: '>=8.0'} 2228 + 2229 + ts-interface-checker@0.1.13: 2230 + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 2231 + 2232 + tslib@2.8.1: 2233 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 2234 + 2235 + type-fest@3.13.1: 2236 + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} 2237 + engines: {node: '>=14.16'} 2238 + 2239 + type-fest@4.41.0: 2240 + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} 2241 + engines: {node: '>=16'} 2242 + 2243 + typedarray@0.0.6: 2244 + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} 2245 + 2246 + typescript@5.9.2: 2247 + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} 2248 + engines: {node: '>=14.17'} 2249 + hasBin: true 2250 + 2251 + ufo@1.6.1: 2252 + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} 2253 + 2254 + uhyphen@0.2.0: 2255 + resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} 2256 + 2257 + undici-types@7.10.0: 2258 + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} 2259 + 2260 + unimport@5.2.0: 2261 + resolution: {integrity: sha512-bTuAMMOOqIAyjV4i4UH7P07pO+EsVxmhOzQ2YJ290J6mkLUdozNhb5I/YoOEheeNADC03ent3Qj07X0fWfUpmw==} 2262 + engines: {node: '>=18.12.0'} 2263 + 2264 + universalify@2.0.1: 2265 + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} 2266 + engines: {node: '>= 10.0.0'} 2267 + 2268 + unplugin-utils@0.2.5: 2269 + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} 2270 + engines: {node: '>=18.12.0'} 2271 + 2272 + unplugin@2.3.10: 2273 + resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} 2274 + engines: {node: '>=18.12.0'} 2275 + 2276 + update-browserslist-db@1.1.3: 2277 + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} 2278 + hasBin: true 2279 + peerDependencies: 2280 + browserslist: '>= 4.21.0' 2281 + 2282 + update-notifier@7.3.1: 2283 + resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} 2284 + engines: {node: '>=18'} 2285 + 2286 + util-deprecate@1.0.2: 2287 + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 2288 + 2289 + uuid@8.3.2: 2290 + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 2291 + hasBin: true 2292 + 2293 + vite-node@3.2.4: 2294 + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} 2295 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 2296 + hasBin: true 2297 + 2298 + vite@7.1.5: 2299 + resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==} 2300 + engines: {node: ^20.19.0 || >=22.12.0} 2301 + hasBin: true 2302 + peerDependencies: 2303 + '@types/node': ^20.19.0 || >=22.12.0 2304 + jiti: '>=1.21.0' 2305 + less: ^4.0.0 2306 + lightningcss: ^1.21.0 2307 + sass: ^1.70.0 2308 + sass-embedded: ^1.70.0 2309 + stylus: '>=0.54.8' 2310 + sugarss: ^5.0.0 2311 + terser: ^5.16.0 2312 + tsx: ^4.8.1 2313 + yaml: ^2.4.2 2314 + peerDependenciesMeta: 2315 + '@types/node': 2316 + optional: true 2317 + jiti: 2318 + optional: true 2319 + less: 2320 + optional: true 2321 + lightningcss: 2322 + optional: true 2323 + sass: 2324 + optional: true 2325 + sass-embedded: 2326 + optional: true 2327 + stylus: 2328 + optional: true 2329 + sugarss: 2330 + optional: true 2331 + terser: 2332 + optional: true 2333 + tsx: 2334 + optional: true 2335 + yaml: 2336 + optional: true 2337 + 2338 + watchpack@2.4.4: 2339 + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} 2340 + engines: {node: '>=10.13.0'} 2341 + 2342 + wcwidth@1.0.1: 2343 + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} 2344 + 2345 + web-ext-run@0.2.4: 2346 + resolution: {integrity: sha512-rQicL7OwuqWdQWI33JkSXKcp7cuv1mJG8u3jRQwx/8aDsmhbTHs9ZRmNYOL+LX0wX8edIEQX8jj4bB60GoXtKA==} 2347 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 2348 + 2349 + webpack-virtual-modules@0.6.2: 2350 + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} 2351 + 2352 + when-exit@2.1.4: 2353 + resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} 2354 + 2355 + when@3.7.7: 2356 + resolution: {integrity: sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw==} 2357 + 2358 + which@1.2.4: 2359 + resolution: {integrity: sha512-zDRAqDSBudazdfM9zpiI30Fu9ve47htYXcGi3ln0wfKu2a7SmrT6F3VDoYONu//48V8Vz4TdCRNPjtvyRO3yBA==} 2360 + hasBin: true 2361 + 2362 + which@2.0.2: 2363 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 2364 + engines: {node: '>= 8'} 2365 + hasBin: true 2366 + 2367 + widest-line@5.0.0: 2368 + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} 2369 + engines: {node: '>=18'} 2370 + 2371 + winreg@0.0.12: 2372 + resolution: {integrity: sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==} 2373 + 2374 + wrap-ansi@7.0.0: 2375 + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 2376 + engines: {node: '>=10'} 2377 + 2378 + wrap-ansi@8.1.0: 2379 + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 2380 + engines: {node: '>=12'} 2381 + 2382 + wrap-ansi@9.0.2: 2383 + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} 2384 + engines: {node: '>=18'} 2385 + 2386 + wrappy@1.0.2: 2387 + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 2388 + 2389 + wsl-utils@0.1.0: 2390 + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} 2391 + engines: {node: '>=18'} 2392 + 2393 + wxt@0.20.11: 2394 + resolution: {integrity: sha512-DqqHc/5COs8GR21ii99bANXf/mu6zuDpiXFV1YKNsqO5/PvkrCx5arY0aVPL5IATsuacAnNzdj4eMc1qbzS53Q==} 2395 + hasBin: true 2396 + 2397 + xdg-basedir@5.1.0: 2398 + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} 2399 + engines: {node: '>=12'} 2400 + 2401 + xml2js@0.6.2: 2402 + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} 2403 + engines: {node: '>=4.0.0'} 2404 + 2405 + xmlbuilder@11.0.1: 2406 + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} 2407 + engines: {node: '>=4.0'} 2408 + 2409 + y18n@5.0.8: 2410 + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 2411 + engines: {node: '>=10'} 2412 + 2413 + yallist@3.1.1: 2414 + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 2415 + 2416 + yaml@2.8.1: 2417 + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} 2418 + engines: {node: '>= 14.6'} 2419 + hasBin: true 2420 + 2421 + yargs-parser@20.2.9: 2422 + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} 2423 + engines: {node: '>=10'} 2424 + 2425 + yargs-parser@21.1.1: 2426 + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 2427 + engines: {node: '>=12'} 2428 + 2429 + yargs@16.2.0: 2430 + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} 2431 + engines: {node: '>=10'} 2432 + 2433 + yargs@17.7.2: 2434 + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 2435 + engines: {node: '>=12'} 2436 + 2437 + yauzl@2.10.0: 2438 + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} 2439 + 2440 + zip-dir@2.0.0: 2441 + resolution: {integrity: sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==} 2442 + 2443 + zod@3.25.76: 2444 + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} 2445 + 2446 + snapshots: 2447 + 2448 + '@1natsu/wait-element@4.1.2': 2449 + dependencies: 2450 + defu: 6.1.4 2451 + many-keys-map: 2.0.1 2452 + 2453 + '@aklinker1/rollup-plugin-visualizer@5.12.0(rollup@4.50.1)': 2454 + dependencies: 2455 + open: 8.4.2 2456 + picomatch: 2.3.1 2457 + source-map: 0.7.6 2458 + yargs: 17.7.2 2459 + optionalDependencies: 2460 + rollup: 4.50.1 2461 + 2462 + '@alloc/quick-lru@5.2.0': {} 2463 + 2464 + '@babel/code-frame@7.27.1': 2465 + dependencies: 2466 + '@babel/helper-validator-identifier': 7.27.1 2467 + js-tokens: 4.0.0 2468 + picocolors: 1.1.1 2469 + 2470 + '@babel/compat-data@7.28.4': {} 2471 + 2472 + '@babel/core@7.28.4': 2473 + dependencies: 2474 + '@babel/code-frame': 7.27.1 2475 + '@babel/generator': 7.28.3 2476 + '@babel/helper-compilation-targets': 7.27.2 2477 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) 2478 + '@babel/helpers': 7.28.4 2479 + '@babel/parser': 7.28.4 2480 + '@babel/template': 7.27.2 2481 + '@babel/traverse': 7.28.4 2482 + '@babel/types': 7.28.4 2483 + '@jridgewell/remapping': 2.3.5 2484 + convert-source-map: 2.0.0 2485 + debug: 4.4.1 2486 + gensync: 1.0.0-beta.2 2487 + json5: 2.2.3 2488 + semver: 6.3.1 2489 + transitivePeerDependencies: 2490 + - supports-color 2491 + 2492 + '@babel/generator@7.28.3': 2493 + dependencies: 2494 + '@babel/parser': 7.28.4 2495 + '@babel/types': 7.28.4 2496 + '@jridgewell/gen-mapping': 0.3.13 2497 + '@jridgewell/trace-mapping': 0.3.31 2498 + jsesc: 3.1.0 2499 + 2500 + '@babel/helper-compilation-targets@7.27.2': 2501 + dependencies: 2502 + '@babel/compat-data': 7.28.4 2503 + '@babel/helper-validator-option': 7.27.1 2504 + browserslist: 4.26.0 2505 + lru-cache: 5.1.1 2506 + semver: 6.3.1 2507 + 2508 + '@babel/helper-globals@7.28.0': {} 2509 + 2510 + '@babel/helper-module-imports@7.27.1': 2511 + dependencies: 2512 + '@babel/traverse': 7.28.4 2513 + '@babel/types': 7.28.4 2514 + transitivePeerDependencies: 2515 + - supports-color 2516 + 2517 + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': 2518 + dependencies: 2519 + '@babel/core': 7.28.4 2520 + '@babel/helper-module-imports': 7.27.1 2521 + '@babel/helper-validator-identifier': 7.27.1 2522 + '@babel/traverse': 7.28.4 2523 + transitivePeerDependencies: 2524 + - supports-color 2525 + 2526 + '@babel/helper-plugin-utils@7.27.1': {} 2527 + 2528 + '@babel/helper-string-parser@7.27.1': {} 2529 + 2530 + '@babel/helper-validator-identifier@7.27.1': {} 2531 + 2532 + '@babel/helper-validator-option@7.27.1': {} 2533 + 2534 + '@babel/helpers@7.28.4': 2535 + dependencies: 2536 + '@babel/template': 7.27.2 2537 + '@babel/types': 7.28.4 2538 + 2539 + '@babel/parser@7.28.4': 2540 + dependencies: 2541 + '@babel/types': 7.28.4 2542 + 2543 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': 2544 + dependencies: 2545 + '@babel/core': 7.28.4 2546 + '@babel/helper-plugin-utils': 7.27.1 2547 + 2548 + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': 2549 + dependencies: 2550 + '@babel/core': 7.28.4 2551 + '@babel/helper-plugin-utils': 7.27.1 2552 + 2553 + '@babel/runtime@7.28.2': {} 2554 + 2555 + '@babel/template@7.27.2': 2556 + dependencies: 2557 + '@babel/code-frame': 7.27.1 2558 + '@babel/parser': 7.28.4 2559 + '@babel/types': 7.28.4 2560 + 2561 + '@babel/traverse@7.28.4': 2562 + dependencies: 2563 + '@babel/code-frame': 7.27.1 2564 + '@babel/generator': 7.28.3 2565 + '@babel/helper-globals': 7.28.0 2566 + '@babel/parser': 7.28.4 2567 + '@babel/template': 7.27.2 2568 + '@babel/types': 7.28.4 2569 + debug: 4.4.1 2570 + transitivePeerDependencies: 2571 + - supports-color 2572 + 2573 + '@babel/types@7.28.4': 2574 + dependencies: 2575 + '@babel/helper-string-parser': 7.27.1 2576 + '@babel/helper-validator-identifier': 7.27.1 2577 + 2578 + '@biomejs/biome@2.2.4': 2579 + optionalDependencies: 2580 + '@biomejs/cli-darwin-arm64': 2.2.4 2581 + '@biomejs/cli-darwin-x64': 2.2.4 2582 + '@biomejs/cli-linux-arm64': 2.2.4 2583 + '@biomejs/cli-linux-arm64-musl': 2.2.4 2584 + '@biomejs/cli-linux-x64': 2.2.4 2585 + '@biomejs/cli-linux-x64-musl': 2.2.4 2586 + '@biomejs/cli-win32-arm64': 2.2.4 2587 + '@biomejs/cli-win32-x64': 2.2.4 2588 + 2589 + '@biomejs/cli-darwin-arm64@2.2.4': 2590 + optional: true 2591 + 2592 + '@biomejs/cli-darwin-x64@2.2.4': 2593 + optional: true 2594 + 2595 + '@biomejs/cli-linux-arm64-musl@2.2.4': 2596 + optional: true 2597 + 2598 + '@biomejs/cli-linux-arm64@2.2.4': 2599 + optional: true 2600 + 2601 + '@biomejs/cli-linux-x64-musl@2.2.4': 2602 + optional: true 2603 + 2604 + '@biomejs/cli-linux-x64@2.2.4': 2605 + optional: true 2606 + 2607 + '@biomejs/cli-win32-arm64@2.2.4': 2608 + optional: true 2609 + 2610 + '@biomejs/cli-win32-x64@2.2.4': 2611 + optional: true 2612 + 2613 + '@devicefarmer/adbkit-logcat@2.1.3': {} 2614 + 2615 + '@devicefarmer/adbkit-monkey@1.2.1': {} 2616 + 2617 + '@devicefarmer/adbkit@3.3.8': 2618 + dependencies: 2619 + '@devicefarmer/adbkit-logcat': 2.1.3 2620 + '@devicefarmer/adbkit-monkey': 1.2.1 2621 + bluebird: 3.7.2 2622 + commander: 9.5.0 2623 + debug: 4.3.7 2624 + node-forge: 1.3.1 2625 + split: 1.0.1 2626 + transitivePeerDependencies: 2627 + - supports-color 2628 + 2629 + '@emnapi/runtime@1.4.5': 2630 + dependencies: 2631 + tslib: 2.8.1 2632 + optional: true 2633 + 2634 + '@esbuild/aix-ppc64@0.25.9': 2635 + optional: true 2636 + 2637 + '@esbuild/android-arm64@0.25.9': 2638 + optional: true 2639 + 2640 + '@esbuild/android-arm@0.25.9': 2641 + optional: true 2642 + 2643 + '@esbuild/android-x64@0.25.9': 2644 + optional: true 2645 + 2646 + '@esbuild/darwin-arm64@0.25.9': 2647 + optional: true 2648 + 2649 + '@esbuild/darwin-x64@0.25.9': 2650 + optional: true 2651 + 2652 + '@esbuild/freebsd-arm64@0.25.9': 2653 + optional: true 2654 + 2655 + '@esbuild/freebsd-x64@0.25.9': 2656 + optional: true 2657 + 2658 + '@esbuild/linux-arm64@0.25.9': 2659 + optional: true 2660 + 2661 + '@esbuild/linux-arm@0.25.9': 2662 + optional: true 2663 + 2664 + '@esbuild/linux-ia32@0.25.9': 2665 + optional: true 2666 + 2667 + '@esbuild/linux-loong64@0.25.9': 2668 + optional: true 2669 + 2670 + '@esbuild/linux-mips64el@0.25.9': 2671 + optional: true 2672 + 2673 + '@esbuild/linux-ppc64@0.25.9': 2674 + optional: true 2675 + 2676 + '@esbuild/linux-riscv64@0.25.9': 2677 + optional: true 2678 + 2679 + '@esbuild/linux-s390x@0.25.9': 2680 + optional: true 2681 + 2682 + '@esbuild/linux-x64@0.25.9': 2683 + optional: true 2684 + 2685 + '@esbuild/netbsd-arm64@0.25.9': 2686 + optional: true 2687 + 2688 + '@esbuild/netbsd-x64@0.25.9': 2689 + optional: true 2690 + 2691 + '@esbuild/openbsd-arm64@0.25.9': 2692 + optional: true 2693 + 2694 + '@esbuild/openbsd-x64@0.25.9': 2695 + optional: true 2696 + 2697 + '@esbuild/openharmony-arm64@0.25.9': 2698 + optional: true 2699 + 2700 + '@esbuild/sunos-x64@0.25.9': 2701 + optional: true 2702 + 2703 + '@esbuild/win32-arm64@0.25.9': 2704 + optional: true 2705 + 2706 + '@esbuild/win32-ia32@0.25.9': 2707 + optional: true 2708 + 2709 + '@esbuild/win32-x64@0.25.9': 2710 + optional: true 2711 + 2712 + '@img/sharp-darwin-arm64@0.34.3': 2713 + optionalDependencies: 2714 + '@img/sharp-libvips-darwin-arm64': 1.2.0 2715 + optional: true 2716 + 2717 + '@img/sharp-darwin-x64@0.34.3': 2718 + optionalDependencies: 2719 + '@img/sharp-libvips-darwin-x64': 1.2.0 2720 + optional: true 2721 + 2722 + '@img/sharp-libvips-darwin-arm64@1.2.0': 2723 + optional: true 2724 + 2725 + '@img/sharp-libvips-darwin-x64@1.2.0': 2726 + optional: true 2727 + 2728 + '@img/sharp-libvips-linux-arm64@1.2.0': 2729 + optional: true 2730 + 2731 + '@img/sharp-libvips-linux-arm@1.2.0': 2732 + optional: true 2733 + 2734 + '@img/sharp-libvips-linux-ppc64@1.2.0': 2735 + optional: true 2736 + 2737 + '@img/sharp-libvips-linux-s390x@1.2.0': 2738 + optional: true 2739 + 2740 + '@img/sharp-libvips-linux-x64@1.2.0': 2741 + optional: true 2742 + 2743 + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': 2744 + optional: true 2745 + 2746 + '@img/sharp-libvips-linuxmusl-x64@1.2.0': 2747 + optional: true 2748 + 2749 + '@img/sharp-linux-arm64@0.34.3': 2750 + optionalDependencies: 2751 + '@img/sharp-libvips-linux-arm64': 1.2.0 2752 + optional: true 2753 + 2754 + '@img/sharp-linux-arm@0.34.3': 2755 + optionalDependencies: 2756 + '@img/sharp-libvips-linux-arm': 1.2.0 2757 + optional: true 2758 + 2759 + '@img/sharp-linux-ppc64@0.34.3': 2760 + optionalDependencies: 2761 + '@img/sharp-libvips-linux-ppc64': 1.2.0 2762 + optional: true 2763 + 2764 + '@img/sharp-linux-s390x@0.34.3': 2765 + optionalDependencies: 2766 + '@img/sharp-libvips-linux-s390x': 1.2.0 2767 + optional: true 2768 + 2769 + '@img/sharp-linux-x64@0.34.3': 2770 + optionalDependencies: 2771 + '@img/sharp-libvips-linux-x64': 1.2.0 2772 + optional: true 2773 + 2774 + '@img/sharp-linuxmusl-arm64@0.34.3': 2775 + optionalDependencies: 2776 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 2777 + optional: true 2778 + 2779 + '@img/sharp-linuxmusl-x64@0.34.3': 2780 + optionalDependencies: 2781 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 2782 + optional: true 2783 + 2784 + '@img/sharp-wasm32@0.34.3': 2785 + dependencies: 2786 + '@emnapi/runtime': 1.4.5 2787 + optional: true 2788 + 2789 + '@img/sharp-win32-arm64@0.34.3': 2790 + optional: true 2791 + 2792 + '@img/sharp-win32-ia32@0.34.3': 2793 + optional: true 2794 + 2795 + '@img/sharp-win32-x64@0.34.3': 2796 + optional: true 2797 + 2798 + '@isaacs/balanced-match@4.0.1': {} 2799 + 2800 + '@isaacs/brace-expansion@5.0.0': 2801 + dependencies: 2802 + '@isaacs/balanced-match': 4.0.1 2803 + 2804 + '@isaacs/cliui@8.0.2': 2805 + dependencies: 2806 + string-width: 5.1.2 2807 + string-width-cjs: string-width@4.2.3 2808 + strip-ansi: 7.1.0 2809 + strip-ansi-cjs: strip-ansi@6.0.1 2810 + wrap-ansi: 8.1.0 2811 + wrap-ansi-cjs: wrap-ansi@7.0.0 2812 + 2813 + '@jridgewell/gen-mapping@0.3.13': 2814 + dependencies: 2815 + '@jridgewell/sourcemap-codec': 1.5.5 2816 + '@jridgewell/trace-mapping': 0.3.30 2817 + 2818 + '@jridgewell/remapping@2.3.5': 2819 + dependencies: 2820 + '@jridgewell/gen-mapping': 0.3.13 2821 + '@jridgewell/trace-mapping': 0.3.31 2822 + 2823 + '@jridgewell/resolve-uri@3.1.2': {} 2824 + 2825 + '@jridgewell/sourcemap-codec@1.5.5': {} 2826 + 2827 + '@jridgewell/trace-mapping@0.3.30': 2828 + dependencies: 2829 + '@jridgewell/resolve-uri': 3.1.2 2830 + '@jridgewell/sourcemap-codec': 1.5.5 2831 + 2832 + '@jridgewell/trace-mapping@0.3.31': 2833 + dependencies: 2834 + '@jridgewell/resolve-uri': 3.1.2 2835 + '@jridgewell/sourcemap-codec': 1.5.5 2836 + 2837 + '@nodelib/fs.scandir@2.1.5': 2838 + dependencies: 2839 + '@nodelib/fs.stat': 2.0.5 2840 + run-parallel: 1.2.0 2841 + 2842 + '@nodelib/fs.stat@2.0.5': {} 2843 + 2844 + '@nodelib/fs.walk@1.2.8': 2845 + dependencies: 2846 + '@nodelib/fs.scandir': 2.1.5 2847 + fastq: 1.19.1 2848 + 2849 + '@pkgjs/parseargs@0.11.0': 2850 + optional: true 2851 + 2852 + '@pnpm/config.env-replace@1.1.0': {} 2853 + 2854 + '@pnpm/network.ca-file@1.0.2': 2855 + dependencies: 2856 + graceful-fs: 4.2.10 2857 + 2858 + '@pnpm/npm-conf@2.3.1': 2859 + dependencies: 2860 + '@pnpm/config.env-replace': 1.1.0 2861 + '@pnpm/network.ca-file': 1.0.2 2862 + config-chain: 1.1.13 2863 + 2864 + '@rolldown/pluginutils@1.0.0-beta.34': {} 2865 + 2866 + '@rollup/rollup-android-arm-eabi@4.50.1': 2867 + optional: true 2868 + 2869 + '@rollup/rollup-android-arm64@4.50.1': 2870 + optional: true 2871 + 2872 + '@rollup/rollup-darwin-arm64@4.50.1': 2873 + optional: true 2874 + 2875 + '@rollup/rollup-darwin-x64@4.50.1': 2876 + optional: true 2877 + 2878 + '@rollup/rollup-freebsd-arm64@4.50.1': 2879 + optional: true 2880 + 2881 + '@rollup/rollup-freebsd-x64@4.50.1': 2882 + optional: true 2883 + 2884 + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': 2885 + optional: true 2886 + 2887 + '@rollup/rollup-linux-arm-musleabihf@4.50.1': 2888 + optional: true 2889 + 2890 + '@rollup/rollup-linux-arm64-gnu@4.50.1': 2891 + optional: true 2892 + 2893 + '@rollup/rollup-linux-arm64-musl@4.50.1': 2894 + optional: true 2895 + 2896 + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': 2897 + optional: true 2898 + 2899 + '@rollup/rollup-linux-ppc64-gnu@4.50.1': 2900 + optional: true 2901 + 2902 + '@rollup/rollup-linux-riscv64-gnu@4.50.1': 2903 + optional: true 2904 + 2905 + '@rollup/rollup-linux-riscv64-musl@4.50.1': 2906 + optional: true 2907 + 2908 + '@rollup/rollup-linux-s390x-gnu@4.50.1': 2909 + optional: true 2910 + 2911 + '@rollup/rollup-linux-x64-gnu@4.50.1': 2912 + optional: true 2913 + 2914 + '@rollup/rollup-linux-x64-musl@4.50.1': 2915 + optional: true 2916 + 2917 + '@rollup/rollup-openharmony-arm64@4.50.1': 2918 + optional: true 2919 + 2920 + '@rollup/rollup-win32-arm64-msvc@4.50.1': 2921 + optional: true 2922 + 2923 + '@rollup/rollup-win32-ia32-msvc@4.50.1': 2924 + optional: true 2925 + 2926 + '@rollup/rollup-win32-x64-msvc@4.50.1': 2927 + optional: true 2928 + 2929 + '@types/babel__core@7.20.5': 2930 + dependencies: 2931 + '@babel/parser': 7.28.4 2932 + '@babel/types': 7.28.4 2933 + '@types/babel__generator': 7.27.0 2934 + '@types/babel__template': 7.4.4 2935 + '@types/babel__traverse': 7.28.0 2936 + 2937 + '@types/babel__generator@7.27.0': 2938 + dependencies: 2939 + '@babel/types': 7.28.4 2940 + 2941 + '@types/babel__template@7.4.4': 2942 + dependencies: 2943 + '@babel/parser': 7.28.4 2944 + '@babel/types': 7.28.4 2945 + 2946 + '@types/babel__traverse@7.28.0': 2947 + dependencies: 2948 + '@babel/types': 7.28.4 2949 + 2950 + '@types/estree@1.0.8': {} 2951 + 2952 + '@types/filesystem@0.0.36': 2953 + dependencies: 2954 + '@types/filewriter': 0.0.33 2955 + 2956 + '@types/filewriter@0.0.33': {} 2957 + 2958 + '@types/har-format@1.2.16': {} 2959 + 2960 + '@types/minimatch@3.0.5': {} 2961 + 2962 + '@types/node@24.3.2': 2963 + dependencies: 2964 + undici-types: 7.10.0 2965 + 2966 + '@types/react-dom@19.1.9(@types/react@19.1.13)': 2967 + dependencies: 2968 + '@types/react': 19.1.13 2969 + 2970 + '@types/react@19.1.13': 2971 + dependencies: 2972 + csstype: 3.1.3 2973 + 2974 + '@types/yauzl@2.10.3': 2975 + dependencies: 2976 + '@types/node': 24.3.2 2977 + optional: true 2978 + 2979 + '@vitejs/plugin-react@5.0.2(vite@7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1))': 2980 + dependencies: 2981 + '@babel/core': 7.28.4 2982 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) 2983 + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) 2984 + '@rolldown/pluginutils': 1.0.0-beta.34 2985 + '@types/babel__core': 7.20.5 2986 + react-refresh: 0.17.0 2987 + vite: 7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1) 2988 + transitivePeerDependencies: 2989 + - supports-color 2990 + 2991 + '@webext-core/fake-browser@1.3.2': 2992 + dependencies: 2993 + lodash.merge: 4.6.2 2994 + 2995 + '@webext-core/isolated-element@1.1.2': 2996 + dependencies: 2997 + is-potential-custom-element-name: 1.0.1 2998 + 2999 + '@webext-core/match-patterns@1.0.3': {} 3000 + 3001 + '@wxt-dev/auto-icons@1.1.0(wxt@0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1))': 3002 + dependencies: 3003 + defu: 6.1.4 3004 + fs-extra: 11.3.1 3005 + sharp: 0.34.3 3006 + wxt: 0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1) 3007 + 3008 + '@wxt-dev/browser@0.1.4': 3009 + dependencies: 3010 + '@types/filesystem': 0.0.36 3011 + '@types/har-format': 1.2.16 3012 + 3013 + '@wxt-dev/module-react@1.1.5(vite@7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1))(wxt@0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1))': 3014 + dependencies: 3015 + '@vitejs/plugin-react': 5.0.2(vite@7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1)) 3016 + wxt: 0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1) 3017 + transitivePeerDependencies: 3018 + - supports-color 3019 + - vite 3020 + 3021 + '@wxt-dev/storage@1.2.0': 3022 + dependencies: 3023 + '@wxt-dev/browser': 0.1.4 3024 + async-mutex: 0.5.0 3025 + dequal: 2.0.3 3026 + 3027 + acorn@8.15.0: {} 3028 + 3029 + adm-zip@0.5.16: {} 3030 + 3031 + ansi-align@3.0.1: 3032 + dependencies: 3033 + string-width: 4.2.3 3034 + 3035 + ansi-escapes@7.1.0: 3036 + dependencies: 3037 + environment: 1.1.0 3038 + 3039 + ansi-regex@5.0.1: {} 3040 + 3041 + ansi-regex@6.2.0: {} 3042 + 3043 + ansi-regex@6.2.2: {} 3044 + 3045 + ansi-styles@4.3.0: 3046 + dependencies: 3047 + color-convert: 2.0.1 3048 + 3049 + ansi-styles@6.2.1: {} 3050 + 3051 + ansi-styles@6.2.3: {} 3052 + 3053 + any-promise@1.3.0: {} 3054 + 3055 + anymatch@3.1.3: 3056 + dependencies: 3057 + normalize-path: 3.0.0 3058 + picomatch: 2.3.1 3059 + 3060 + arg@5.0.2: {} 3061 + 3062 + array-differ@4.0.0: {} 3063 + 3064 + array-union@3.0.1: {} 3065 + 3066 + async-mutex@0.5.0: 3067 + dependencies: 3068 + tslib: 2.8.1 3069 + 3070 + async@3.2.6: {} 3071 + 3072 + atomic-sleep@1.0.0: {} 3073 + 3074 + atomically@2.0.3: 3075 + dependencies: 3076 + stubborn-fs: 1.2.5 3077 + when-exit: 2.1.4 3078 + 3079 + autoprefixer@10.4.21(postcss@8.5.6): 3080 + dependencies: 3081 + browserslist: 4.25.3 3082 + caniuse-lite: 1.0.30001735 3083 + fraction.js: 4.3.7 3084 + normalize-range: 0.1.2 3085 + picocolors: 1.1.1 3086 + postcss: 8.5.6 3087 + postcss-value-parser: 4.2.0 3088 + 3089 + balanced-match@1.0.2: {} 3090 + 3091 + base64-js@1.5.1: {} 3092 + 3093 + baseline-browser-mapping@2.8.2: {} 3094 + 3095 + binary-extensions@2.3.0: {} 3096 + 3097 + bl@5.1.0: 3098 + dependencies: 3099 + buffer: 6.0.3 3100 + inherits: 2.0.4 3101 + readable-stream: 3.6.2 3102 + 3103 + bluebird@3.7.2: {} 3104 + 3105 + boolbase@1.0.0: {} 3106 + 3107 + boxen@8.0.1: 3108 + dependencies: 3109 + ansi-align: 3.0.1 3110 + camelcase: 8.0.0 3111 + chalk: 5.6.2 3112 + cli-boxes: 3.0.0 3113 + string-width: 7.2.0 3114 + type-fest: 4.41.0 3115 + widest-line: 5.0.0 3116 + wrap-ansi: 9.0.2 3117 + 3118 + brace-expansion@1.1.12: 3119 + dependencies: 3120 + balanced-match: 1.0.2 3121 + concat-map: 0.0.1 3122 + 3123 + brace-expansion@2.0.2: 3124 + dependencies: 3125 + balanced-match: 1.0.2 3126 + 3127 + braces@3.0.3: 3128 + dependencies: 3129 + fill-range: 7.1.1 3130 + 3131 + browserslist@4.25.3: 3132 + dependencies: 3133 + caniuse-lite: 1.0.30001735 3134 + electron-to-chromium: 1.5.207 3135 + node-releases: 2.0.19 3136 + update-browserslist-db: 1.1.3(browserslist@4.25.3) 3137 + 3138 + browserslist@4.26.0: 3139 + dependencies: 3140 + baseline-browser-mapping: 2.8.2 3141 + caniuse-lite: 1.0.30001741 3142 + electron-to-chromium: 1.5.218 3143 + node-releases: 2.0.21 3144 + update-browserslist-db: 1.1.3(browserslist@4.26.0) 3145 + 3146 + buffer-crc32@0.2.13: {} 3147 + 3148 + buffer-from@1.1.2: {} 3149 + 3150 + buffer@6.0.3: 3151 + dependencies: 3152 + base64-js: 1.5.1 3153 + ieee754: 1.2.1 3154 + 3155 + bundle-name@4.1.0: 3156 + dependencies: 3157 + run-applescript: 7.1.0 3158 + 3159 + c12@3.2.0(magicast@0.3.5): 3160 + dependencies: 3161 + chokidar: 4.0.3 3162 + confbox: 0.2.2 3163 + defu: 6.1.4 3164 + dotenv: 17.2.2 3165 + exsolve: 1.0.7 3166 + giget: 2.0.0 3167 + jiti: 2.5.1 3168 + ohash: 2.0.11 3169 + pathe: 2.0.3 3170 + perfect-debounce: 1.0.0 3171 + pkg-types: 2.3.0 3172 + rc9: 2.1.2 3173 + optionalDependencies: 3174 + magicast: 0.3.5 3175 + 3176 + cac@6.7.14: {} 3177 + 3178 + camelcase-css@2.0.1: {} 3179 + 3180 + camelcase@8.0.0: {} 3181 + 3182 + caniuse-lite@1.0.30001735: {} 3183 + 3184 + caniuse-lite@1.0.30001741: {} 3185 + 3186 + chalk@4.1.2: 3187 + dependencies: 3188 + ansi-styles: 4.3.0 3189 + supports-color: 7.2.0 3190 + 3191 + chalk@5.6.2: {} 3192 + 3193 + chokidar@3.6.0: 3194 + dependencies: 3195 + anymatch: 3.1.3 3196 + braces: 3.0.3 3197 + glob-parent: 5.1.2 3198 + is-binary-path: 2.1.0 3199 + is-glob: 4.0.3 3200 + normalize-path: 3.0.0 3201 + readdirp: 3.6.0 3202 + optionalDependencies: 3203 + fsevents: 2.3.3 3204 + 3205 + chokidar@4.0.3: 3206 + dependencies: 3207 + readdirp: 4.1.2 3208 + 3209 + chrome-launcher@1.2.0: 3210 + dependencies: 3211 + '@types/node': 24.3.2 3212 + escape-string-regexp: 4.0.0 3213 + is-wsl: 2.2.0 3214 + lighthouse-logger: 2.0.2 3215 + transitivePeerDependencies: 3216 + - supports-color 3217 + 3218 + ci-info@4.3.0: {} 3219 + 3220 + citty@0.1.6: 3221 + dependencies: 3222 + consola: 3.4.2 3223 + 3224 + cli-boxes@3.0.0: {} 3225 + 3226 + cli-cursor@4.0.0: 3227 + dependencies: 3228 + restore-cursor: 4.0.0 3229 + 3230 + cli-cursor@5.0.0: 3231 + dependencies: 3232 + restore-cursor: 5.1.0 3233 + 3234 + cli-highlight@2.1.11: 3235 + dependencies: 3236 + chalk: 4.1.2 3237 + highlight.js: 10.7.3 3238 + mz: 2.7.0 3239 + parse5: 5.1.1 3240 + parse5-htmlparser2-tree-adapter: 6.0.1 3241 + yargs: 16.2.0 3242 + 3243 + cli-spinners@2.9.2: {} 3244 + 3245 + cli-truncate@4.0.0: 3246 + dependencies: 3247 + slice-ansi: 5.0.0 3248 + string-width: 7.2.0 3249 + 3250 + cliui@7.0.4: 3251 + dependencies: 3252 + string-width: 4.2.3 3253 + strip-ansi: 6.0.1 3254 + wrap-ansi: 7.0.0 3255 + 3256 + cliui@8.0.1: 3257 + dependencies: 3258 + string-width: 4.2.3 3259 + strip-ansi: 6.0.1 3260 + wrap-ansi: 7.0.0 3261 + 3262 + clone@1.0.4: {} 3263 + 3264 + color-convert@2.0.1: 3265 + dependencies: 3266 + color-name: 1.1.4 3267 + 3268 + color-name@1.1.4: {} 3269 + 3270 + color-string@1.9.1: 3271 + dependencies: 3272 + color-name: 1.1.4 3273 + simple-swizzle: 0.2.2 3274 + 3275 + color@4.2.3: 3276 + dependencies: 3277 + color-convert: 2.0.1 3278 + color-string: 1.9.1 3279 + 3280 + colorette@2.0.20: {} 3281 + 3282 + commander@2.9.0: 3283 + dependencies: 3284 + graceful-readlink: 1.0.1 3285 + 3286 + commander@4.1.1: {} 3287 + 3288 + commander@9.5.0: {} 3289 + 3290 + concat-map@0.0.1: {} 3291 + 3292 + concat-stream@1.6.2: 3293 + dependencies: 3294 + buffer-from: 1.1.2 3295 + inherits: 2.0.4 3296 + readable-stream: 2.3.8 3297 + typedarray: 0.0.6 3298 + 3299 + confbox@0.1.8: {} 3300 + 3301 + confbox@0.2.2: {} 3302 + 3303 + config-chain@1.1.13: 3304 + dependencies: 3305 + ini: 1.3.8 3306 + proto-list: 1.2.4 3307 + 3308 + configstore@7.0.0: 3309 + dependencies: 3310 + atomically: 2.0.3 3311 + dot-prop: 9.0.0 3312 + graceful-fs: 4.2.11 3313 + xdg-basedir: 5.1.0 3314 + 3315 + consola@3.4.2: {} 3316 + 3317 + convert-source-map@2.0.0: {} 3318 + 3319 + core-util-is@1.0.3: {} 3320 + 3321 + cross-spawn@7.0.6: 3322 + dependencies: 3323 + path-key: 3.1.1 3324 + shebang-command: 2.0.0 3325 + which: 2.0.2 3326 + 3327 + css-select@5.2.2: 3328 + dependencies: 3329 + boolbase: 1.0.0 3330 + css-what: 6.2.2 3331 + domhandler: 5.0.3 3332 + domutils: 3.2.2 3333 + nth-check: 2.1.1 3334 + 3335 + css-what@6.2.2: {} 3336 + 3337 + cssesc@3.0.0: {} 3338 + 3339 + cssom@0.5.0: {} 3340 + 3341 + csstype@3.1.3: {} 3342 + 3343 + debounce@1.2.1: {} 3344 + 3345 + debug@4.3.7: 3346 + dependencies: 3347 + ms: 2.1.3 3348 + 3349 + debug@4.4.1: 3350 + dependencies: 3351 + ms: 2.1.3 3352 + 3353 + deep-extend@0.6.0: {} 3354 + 3355 + default-browser-id@5.0.0: {} 3356 + 3357 + default-browser@5.2.1: 3358 + dependencies: 3359 + bundle-name: 4.1.0 3360 + default-browser-id: 5.0.0 3361 + 3362 + defaults@1.0.4: 3363 + dependencies: 3364 + clone: 1.0.4 3365 + 3366 + define-lazy-prop@2.0.0: {} 3367 + 3368 + define-lazy-prop@3.0.0: {} 3369 + 3370 + defu@6.1.4: {} 3371 + 3372 + dequal@2.0.3: {} 3373 + 3374 + destr@2.0.5: {} 3375 + 3376 + detect-libc@2.0.4: {} 3377 + 3378 + didyoumean@1.2.2: {} 3379 + 3380 + dlv@1.1.3: {} 3381 + 3382 + dom-serializer@2.0.0: 3383 + dependencies: 3384 + domelementtype: 2.3.0 3385 + domhandler: 5.0.3 3386 + entities: 4.5.0 3387 + 3388 + domelementtype@2.3.0: {} 3389 + 3390 + domhandler@5.0.3: 3391 + dependencies: 3392 + domelementtype: 2.3.0 3393 + 3394 + domutils@3.2.2: 3395 + dependencies: 3396 + dom-serializer: 2.0.0 3397 + domelementtype: 2.3.0 3398 + domhandler: 5.0.3 3399 + 3400 + dot-prop@9.0.0: 3401 + dependencies: 3402 + type-fest: 4.41.0 3403 + 3404 + dotenv-expand@12.0.3: 3405 + dependencies: 3406 + dotenv: 16.6.1 3407 + 3408 + dotenv@16.6.1: {} 3409 + 3410 + dotenv@17.2.2: {} 3411 + 3412 + eastasianwidth@0.2.0: {} 3413 + 3414 + electron-to-chromium@1.5.207: {} 3415 + 3416 + electron-to-chromium@1.5.218: {} 3417 + 3418 + emoji-regex@10.5.0: {} 3419 + 3420 + emoji-regex@8.0.0: {} 3421 + 3422 + emoji-regex@9.2.2: {} 3423 + 3424 + end-of-stream@1.4.5: 3425 + dependencies: 3426 + once: 1.4.0 3427 + 3428 + entities@4.5.0: {} 3429 + 3430 + entities@6.0.1: {} 3431 + 3432 + environment@1.1.0: {} 3433 + 3434 + error-ex@1.3.2: 3435 + dependencies: 3436 + is-arrayish: 0.2.1 3437 + 3438 + es-module-lexer@1.7.0: {} 3439 + 3440 + es6-error@4.1.1: {} 3441 + 3442 + esbuild@0.25.9: 3443 + optionalDependencies: 3444 + '@esbuild/aix-ppc64': 0.25.9 3445 + '@esbuild/android-arm': 0.25.9 3446 + '@esbuild/android-arm64': 0.25.9 3447 + '@esbuild/android-x64': 0.25.9 3448 + '@esbuild/darwin-arm64': 0.25.9 3449 + '@esbuild/darwin-x64': 0.25.9 3450 + '@esbuild/freebsd-arm64': 0.25.9 3451 + '@esbuild/freebsd-x64': 0.25.9 3452 + '@esbuild/linux-arm': 0.25.9 3453 + '@esbuild/linux-arm64': 0.25.9 3454 + '@esbuild/linux-ia32': 0.25.9 3455 + '@esbuild/linux-loong64': 0.25.9 3456 + '@esbuild/linux-mips64el': 0.25.9 3457 + '@esbuild/linux-ppc64': 0.25.9 3458 + '@esbuild/linux-riscv64': 0.25.9 3459 + '@esbuild/linux-s390x': 0.25.9 3460 + '@esbuild/linux-x64': 0.25.9 3461 + '@esbuild/netbsd-arm64': 0.25.9 3462 + '@esbuild/netbsd-x64': 0.25.9 3463 + '@esbuild/openbsd-arm64': 0.25.9 3464 + '@esbuild/openbsd-x64': 0.25.9 3465 + '@esbuild/openharmony-arm64': 0.25.9 3466 + '@esbuild/sunos-x64': 0.25.9 3467 + '@esbuild/win32-arm64': 0.25.9 3468 + '@esbuild/win32-ia32': 0.25.9 3469 + '@esbuild/win32-x64': 0.25.9 3470 + 3471 + escalade@3.2.0: {} 3472 + 3473 + escape-goat@4.0.0: {} 3474 + 3475 + escape-string-regexp@4.0.0: {} 3476 + 3477 + escape-string-regexp@5.0.0: {} 3478 + 3479 + estree-walker@3.0.3: 3480 + dependencies: 3481 + '@types/estree': 1.0.8 3482 + 3483 + eventemitter3@5.0.1: {} 3484 + 3485 + exsolve@1.0.7: {} 3486 + 3487 + extract-zip@2.0.1: 3488 + dependencies: 3489 + debug: 4.4.1 3490 + get-stream: 5.2.0 3491 + yauzl: 2.10.0 3492 + optionalDependencies: 3493 + '@types/yauzl': 2.10.3 3494 + transitivePeerDependencies: 3495 + - supports-color 3496 + 3497 + fast-glob@3.3.3: 3498 + dependencies: 3499 + '@nodelib/fs.stat': 2.0.5 3500 + '@nodelib/fs.walk': 1.2.8 3501 + glob-parent: 5.1.2 3502 + merge2: 1.4.1 3503 + micromatch: 4.0.8 3504 + 3505 + fast-redact@3.5.0: {} 3506 + 3507 + fastq@1.19.1: 3508 + dependencies: 3509 + reusify: 1.1.0 3510 + 3511 + fd-slicer@1.1.0: 3512 + dependencies: 3513 + pend: 1.2.0 3514 + 3515 + fdir@6.5.0(picomatch@4.0.3): 3516 + optionalDependencies: 3517 + picomatch: 4.0.3 3518 + 3519 + filesize@11.0.2: {} 3520 + 3521 + fill-range@7.1.1: 3522 + dependencies: 3523 + to-regex-range: 5.0.1 3524 + 3525 + firefox-profile@4.7.0: 3526 + dependencies: 3527 + adm-zip: 0.5.16 3528 + fs-extra: 11.3.1 3529 + ini: 4.1.3 3530 + minimist: 1.2.8 3531 + xml2js: 0.6.2 3532 + 3533 + foreground-child@3.3.1: 3534 + dependencies: 3535 + cross-spawn: 7.0.6 3536 + signal-exit: 4.1.0 3537 + 3538 + formdata-node@6.0.3: {} 3539 + 3540 + fraction.js@4.3.7: {} 3541 + 3542 + fs-extra@11.3.1: 3543 + dependencies: 3544 + graceful-fs: 4.2.11 3545 + jsonfile: 6.2.0 3546 + universalify: 2.0.1 3547 + 3548 + fsevents@2.3.3: 3549 + optional: true 3550 + 3551 + function-bind@1.1.2: {} 3552 + 3553 + fx-runner@1.4.0: 3554 + dependencies: 3555 + commander: 2.9.0 3556 + shell-quote: 1.7.3 3557 + spawn-sync: 1.0.15 3558 + when: 3.7.7 3559 + which: 1.2.4 3560 + winreg: 0.0.12 3561 + 3562 + gensync@1.0.0-beta.2: {} 3563 + 3564 + get-caller-file@2.0.5: {} 3565 + 3566 + get-east-asian-width@1.4.0: {} 3567 + 3568 + get-port-please@3.2.0: {} 3569 + 3570 + get-stream@5.2.0: 3571 + dependencies: 3572 + pump: 3.0.3 3573 + 3574 + giget@2.0.0: 3575 + dependencies: 3576 + citty: 0.1.6 3577 + consola: 3.4.2 3578 + defu: 6.1.4 3579 + node-fetch-native: 1.6.7 3580 + nypm: 0.6.1 3581 + pathe: 2.0.3 3582 + 3583 + glob-parent@5.1.2: 3584 + dependencies: 3585 + is-glob: 4.0.3 3586 + 3587 + glob-parent@6.0.2: 3588 + dependencies: 3589 + is-glob: 4.0.3 3590 + 3591 + glob-to-regexp@0.4.1: {} 3592 + 3593 + glob@10.4.5: 3594 + dependencies: 3595 + foreground-child: 3.3.1 3596 + jackspeak: 3.4.3 3597 + minimatch: 9.0.5 3598 + minipass: 7.1.2 3599 + package-json-from-dist: 1.0.1 3600 + path-scurry: 1.11.1 3601 + 3602 + global-directory@4.0.1: 3603 + dependencies: 3604 + ini: 4.1.1 3605 + 3606 + graceful-fs@4.2.10: {} 3607 + 3608 + graceful-fs@4.2.11: {} 3609 + 3610 + graceful-readlink@1.0.1: {} 3611 + 3612 + growly@1.3.0: {} 3613 + 3614 + has-flag@4.0.0: {} 3615 + 3616 + hasown@2.0.2: 3617 + dependencies: 3618 + function-bind: 1.1.2 3619 + 3620 + highlight.js@10.7.3: {} 3621 + 3622 + hookable@5.5.3: {} 3623 + 3624 + html-escaper@3.0.3: {} 3625 + 3626 + htmlparser2@10.0.0: 3627 + dependencies: 3628 + domelementtype: 2.3.0 3629 + domhandler: 5.0.3 3630 + domutils: 3.2.2 3631 + entities: 6.0.1 3632 + 3633 + ieee754@1.2.1: {} 3634 + 3635 + immediate@3.0.6: {} 3636 + 3637 + import-meta-resolve@4.2.0: {} 3638 + 3639 + inherits@2.0.4: {} 3640 + 3641 + ini@1.3.8: {} 3642 + 3643 + ini@4.1.1: {} 3644 + 3645 + ini@4.1.3: {} 3646 + 3647 + is-absolute@0.1.7: 3648 + dependencies: 3649 + is-relative: 0.1.3 3650 + 3651 + is-arrayish@0.2.1: {} 3652 + 3653 + is-arrayish@0.3.2: {} 3654 + 3655 + is-binary-path@2.1.0: 3656 + dependencies: 3657 + binary-extensions: 2.3.0 3658 + 3659 + is-core-module@2.16.1: 3660 + dependencies: 3661 + hasown: 2.0.2 3662 + 3663 + is-docker@2.2.1: {} 3664 + 3665 + is-docker@3.0.0: {} 3666 + 3667 + is-extglob@2.1.1: {} 3668 + 3669 + is-fullwidth-code-point@3.0.0: {} 3670 + 3671 + is-fullwidth-code-point@4.0.0: {} 3672 + 3673 + is-fullwidth-code-point@5.1.0: 3674 + dependencies: 3675 + get-east-asian-width: 1.4.0 3676 + 3677 + is-glob@4.0.3: 3678 + dependencies: 3679 + is-extglob: 2.1.1 3680 + 3681 + is-in-ci@1.0.0: {} 3682 + 3683 + is-inside-container@1.0.0: 3684 + dependencies: 3685 + is-docker: 3.0.0 3686 + 3687 + is-installed-globally@1.0.0: 3688 + dependencies: 3689 + global-directory: 4.0.1 3690 + is-path-inside: 4.0.0 3691 + 3692 + is-interactive@2.0.0: {} 3693 + 3694 + is-npm@6.1.0: {} 3695 + 3696 + is-number@7.0.0: {} 3697 + 3698 + is-path-inside@4.0.0: {} 3699 + 3700 + is-plain-object@2.0.4: 3701 + dependencies: 3702 + isobject: 3.0.1 3703 + 3704 + is-potential-custom-element-name@1.0.1: {} 3705 + 3706 + is-primitive@3.0.1: {} 3707 + 3708 + is-relative@0.1.3: {} 3709 + 3710 + is-unicode-supported@1.3.0: {} 3711 + 3712 + is-unicode-supported@2.1.0: {} 3713 + 3714 + is-wsl@2.2.0: 3715 + dependencies: 3716 + is-docker: 2.2.1 3717 + 3718 + is-wsl@3.1.0: 3719 + dependencies: 3720 + is-inside-container: 1.0.0 3721 + 3722 + isarray@1.0.0: {} 3723 + 3724 + isexe@1.1.2: {} 3725 + 3726 + isexe@2.0.0: {} 3727 + 3728 + isobject@3.0.1: {} 3729 + 3730 + jackspeak@3.4.3: 3731 + dependencies: 3732 + '@isaacs/cliui': 8.0.2 3733 + optionalDependencies: 3734 + '@pkgjs/parseargs': 0.11.0 3735 + 3736 + jiti@1.21.7: {} 3737 + 3738 + jiti@2.5.1: {} 3739 + 3740 + js-tokens@4.0.0: {} 3741 + 3742 + js-tokens@9.0.1: {} 3743 + 3744 + jsesc@3.1.0: {} 3745 + 3746 + json-parse-even-better-errors@3.0.2: {} 3747 + 3748 + json5@2.2.3: {} 3749 + 3750 + jsonfile@6.2.0: 3751 + dependencies: 3752 + universalify: 2.0.1 3753 + optionalDependencies: 3754 + graceful-fs: 4.2.11 3755 + 3756 + jszip@3.10.1: 3757 + dependencies: 3758 + lie: 3.3.0 3759 + pako: 1.0.11 3760 + readable-stream: 2.3.8 3761 + setimmediate: 1.0.5 3762 + 3763 + kleur@3.0.3: {} 3764 + 3765 + ky@1.10.0: {} 3766 + 3767 + latest-version@9.0.0: 3768 + dependencies: 3769 + package-json: 10.0.1 3770 + 3771 + lie@3.3.0: 3772 + dependencies: 3773 + immediate: 3.0.6 3774 + 3775 + lighthouse-logger@2.0.2: 3776 + dependencies: 3777 + debug: 4.4.1 3778 + marky: 1.3.0 3779 + transitivePeerDependencies: 3780 + - supports-color 3781 + 3782 + lilconfig@2.1.0: {} 3783 + 3784 + lilconfig@3.1.3: {} 3785 + 3786 + lines-and-columns@1.2.4: {} 3787 + 3788 + lines-and-columns@2.0.4: {} 3789 + 3790 + linkedom@0.18.12: 3791 + dependencies: 3792 + css-select: 5.2.2 3793 + cssom: 0.5.0 3794 + html-escaper: 3.0.3 3795 + htmlparser2: 10.0.0 3796 + uhyphen: 0.2.0 3797 + 3798 + listr2@8.3.3: 3799 + dependencies: 3800 + cli-truncate: 4.0.0 3801 + colorette: 2.0.20 3802 + eventemitter3: 5.0.1 3803 + log-update: 6.1.0 3804 + rfdc: 1.4.1 3805 + wrap-ansi: 9.0.2 3806 + 3807 + local-pkg@1.1.2: 3808 + dependencies: 3809 + mlly: 1.8.0 3810 + pkg-types: 2.3.0 3811 + quansync: 0.2.11 3812 + 3813 + lodash.camelcase@4.3.0: {} 3814 + 3815 + lodash.kebabcase@4.1.1: {} 3816 + 3817 + lodash.merge@4.6.2: {} 3818 + 3819 + lodash.snakecase@4.1.1: {} 3820 + 3821 + log-symbols@5.1.0: 3822 + dependencies: 3823 + chalk: 5.6.2 3824 + is-unicode-supported: 1.3.0 3825 + 3826 + log-symbols@6.0.0: 3827 + dependencies: 3828 + chalk: 5.6.2 3829 + is-unicode-supported: 1.3.0 3830 + 3831 + log-update@6.1.0: 3832 + dependencies: 3833 + ansi-escapes: 7.1.0 3834 + cli-cursor: 5.0.0 3835 + slice-ansi: 7.1.2 3836 + strip-ansi: 7.1.2 3837 + wrap-ansi: 9.0.2 3838 + 3839 + lru-cache@10.4.3: {} 3840 + 3841 + lru-cache@5.1.1: 3842 + dependencies: 3843 + yallist: 3.1.1 3844 + 3845 + magic-string@0.30.19: 3846 + dependencies: 3847 + '@jridgewell/sourcemap-codec': 1.5.5 3848 + 3849 + magicast@0.3.5: 3850 + dependencies: 3851 + '@babel/parser': 7.28.4 3852 + '@babel/types': 7.28.4 3853 + source-map-js: 1.2.1 3854 + 3855 + make-error@1.3.6: {} 3856 + 3857 + many-keys-map@2.0.1: {} 3858 + 3859 + marky@1.3.0: {} 3860 + 3861 + merge2@1.4.1: {} 3862 + 3863 + micromatch@4.0.8: 3864 + dependencies: 3865 + braces: 3.0.3 3866 + picomatch: 2.3.1 3867 + 3868 + mimic-fn@2.1.0: {} 3869 + 3870 + mimic-function@5.0.1: {} 3871 + 3872 + minimatch@10.0.3: 3873 + dependencies: 3874 + '@isaacs/brace-expansion': 5.0.0 3875 + 3876 + minimatch@3.1.2: 3877 + dependencies: 3878 + brace-expansion: 1.1.12 3879 + 3880 + minimatch@9.0.5: 3881 + dependencies: 3882 + brace-expansion: 2.0.2 3883 + 3884 + minimist@1.2.8: {} 3885 + 3886 + minipass@7.1.2: {} 3887 + 3888 + mlly@1.8.0: 3889 + dependencies: 3890 + acorn: 8.15.0 3891 + pathe: 2.0.3 3892 + pkg-types: 1.3.1 3893 + ufo: 1.6.1 3894 + 3895 + ms@2.1.3: {} 3896 + 3897 + multimatch@6.0.0: 3898 + dependencies: 3899 + '@types/minimatch': 3.0.5 3900 + array-differ: 4.0.0 3901 + array-union: 3.0.1 3902 + minimatch: 3.1.2 3903 + 3904 + mz@2.7.0: 3905 + dependencies: 3906 + any-promise: 1.3.0 3907 + object-assign: 4.1.1 3908 + thenify-all: 1.6.0 3909 + 3910 + nano-spawn@1.0.3: {} 3911 + 3912 + nanoid@3.3.11: {} 3913 + 3914 + node-fetch-native@1.6.7: {} 3915 + 3916 + node-forge@1.3.1: {} 3917 + 3918 + node-notifier@10.0.1: 3919 + dependencies: 3920 + growly: 1.3.0 3921 + is-wsl: 2.2.0 3922 + semver: 7.7.2 3923 + shellwords: 0.1.1 3924 + uuid: 8.3.2 3925 + which: 2.0.2 3926 + 3927 + node-releases@2.0.19: {} 3928 + 3929 + node-releases@2.0.21: {} 3930 + 3931 + normalize-path@3.0.0: {} 3932 + 3933 + normalize-range@0.1.2: {} 3934 + 3935 + nth-check@2.1.1: 3936 + dependencies: 3937 + boolbase: 1.0.0 3938 + 3939 + nypm@0.6.1: 3940 + dependencies: 3941 + citty: 0.1.6 3942 + consola: 3.4.2 3943 + pathe: 2.0.3 3944 + pkg-types: 2.3.0 3945 + tinyexec: 1.0.1 3946 + 3947 + object-assign@4.1.1: {} 3948 + 3949 + object-hash@3.0.0: {} 3950 + 3951 + ofetch@1.4.1: 3952 + dependencies: 3953 + destr: 2.0.5 3954 + node-fetch-native: 1.6.7 3955 + ufo: 1.6.1 3956 + 3957 + ohash@2.0.11: {} 3958 + 3959 + on-exit-leak-free@2.1.2: {} 3960 + 3961 + once@1.4.0: 3962 + dependencies: 3963 + wrappy: 1.0.2 3964 + 3965 + onetime@5.1.2: 3966 + dependencies: 3967 + mimic-fn: 2.1.0 3968 + 3969 + onetime@7.0.0: 3970 + dependencies: 3971 + mimic-function: 5.0.1 3972 + 3973 + open@10.2.0: 3974 + dependencies: 3975 + default-browser: 5.2.1 3976 + define-lazy-prop: 3.0.0 3977 + is-inside-container: 1.0.0 3978 + wsl-utils: 0.1.0 3979 + 3980 + open@8.4.2: 3981 + dependencies: 3982 + define-lazy-prop: 2.0.0 3983 + is-docker: 2.2.1 3984 + is-wsl: 2.2.0 3985 + 3986 + ora@6.3.1: 3987 + dependencies: 3988 + chalk: 5.6.2 3989 + cli-cursor: 4.0.0 3990 + cli-spinners: 2.9.2 3991 + is-interactive: 2.0.0 3992 + is-unicode-supported: 1.3.0 3993 + log-symbols: 5.1.0 3994 + stdin-discarder: 0.1.0 3995 + strip-ansi: 7.1.2 3996 + wcwidth: 1.0.1 3997 + 3998 + ora@8.2.0: 3999 + dependencies: 4000 + chalk: 5.6.2 4001 + cli-cursor: 5.0.0 4002 + cli-spinners: 2.9.2 4003 + is-interactive: 2.0.0 4004 + is-unicode-supported: 2.1.0 4005 + log-symbols: 6.0.0 4006 + stdin-discarder: 0.2.2 4007 + string-width: 7.2.0 4008 + strip-ansi: 7.1.2 4009 + 4010 + os-shim@0.1.3: {} 4011 + 4012 + package-json-from-dist@1.0.1: {} 4013 + 4014 + package-json@10.0.1: 4015 + dependencies: 4016 + ky: 1.10.0 4017 + registry-auth-token: 5.1.0 4018 + registry-url: 6.0.1 4019 + semver: 7.7.2 4020 + 4021 + pako@1.0.11: {} 4022 + 4023 + parse-json@7.1.1: 4024 + dependencies: 4025 + '@babel/code-frame': 7.27.1 4026 + error-ex: 1.3.2 4027 + json-parse-even-better-errors: 3.0.2 4028 + lines-and-columns: 2.0.4 4029 + type-fest: 3.13.1 4030 + 4031 + parse5-htmlparser2-tree-adapter@6.0.1: 4032 + dependencies: 4033 + parse5: 6.0.1 4034 + 4035 + parse5@5.1.1: {} 4036 + 4037 + parse5@6.0.1: {} 4038 + 4039 + path-key@3.1.1: {} 4040 + 4041 + path-parse@1.0.7: {} 4042 + 4043 + path-scurry@1.11.1: 4044 + dependencies: 4045 + lru-cache: 10.4.3 4046 + minipass: 7.1.2 4047 + 4048 + pathe@2.0.3: {} 4049 + 4050 + pend@1.2.0: {} 4051 + 4052 + perfect-debounce@1.0.0: {} 4053 + 4054 + perfect-debounce@2.0.0: {} 4055 + 4056 + picocolors@1.1.1: {} 4057 + 4058 + picomatch@2.3.1: {} 4059 + 4060 + picomatch@4.0.3: {} 4061 + 4062 + pify@2.3.0: {} 4063 + 4064 + pino-abstract-transport@2.0.0: 4065 + dependencies: 4066 + split2: 4.2.0 4067 + 4068 + pino-std-serializers@7.0.0: {} 4069 + 4070 + pino@9.7.0: 4071 + dependencies: 4072 + atomic-sleep: 1.0.0 4073 + fast-redact: 3.5.0 4074 + on-exit-leak-free: 2.1.2 4075 + pino-abstract-transport: 2.0.0 4076 + pino-std-serializers: 7.0.0 4077 + process-warning: 5.0.0 4078 + quick-format-unescaped: 4.0.4 4079 + real-require: 0.2.0 4080 + safe-stable-stringify: 2.5.0 4081 + sonic-boom: 4.2.0 4082 + thread-stream: 3.1.0 4083 + 4084 + pirates@4.0.7: {} 4085 + 4086 + pkg-types@1.3.1: 4087 + dependencies: 4088 + confbox: 0.1.8 4089 + mlly: 1.8.0 4090 + pathe: 2.0.3 4091 + 4092 + pkg-types@2.3.0: 4093 + dependencies: 4094 + confbox: 0.2.2 4095 + exsolve: 1.0.7 4096 + pathe: 2.0.3 4097 + 4098 + postcss-import@15.1.0(postcss@8.5.6): 4099 + dependencies: 4100 + postcss: 8.5.6 4101 + postcss-value-parser: 4.2.0 4102 + read-cache: 1.0.0 4103 + resolve: 1.22.10 4104 + 4105 + postcss-js@4.0.1(postcss@8.5.6): 4106 + dependencies: 4107 + camelcase-css: 2.0.1 4108 + postcss: 8.5.6 4109 + 4110 + postcss-load-config@4.0.2(postcss@8.5.6): 4111 + dependencies: 4112 + lilconfig: 3.1.3 4113 + yaml: 2.8.1 4114 + optionalDependencies: 4115 + postcss: 8.5.6 4116 + 4117 + postcss-nested@6.2.0(postcss@8.5.6): 4118 + dependencies: 4119 + postcss: 8.5.6 4120 + postcss-selector-parser: 6.1.2 4121 + 4122 + postcss-selector-parser@6.1.2: 4123 + dependencies: 4124 + cssesc: 3.0.0 4125 + util-deprecate: 1.0.2 4126 + 4127 + postcss-value-parser@4.2.0: {} 4128 + 4129 + postcss@8.5.6: 4130 + dependencies: 4131 + nanoid: 3.3.11 4132 + picocolors: 1.1.1 4133 + source-map-js: 1.2.1 4134 + 4135 + process-nextick-args@2.0.1: {} 4136 + 4137 + process-warning@5.0.0: {} 4138 + 4139 + promise-toolbox@0.21.0: 4140 + dependencies: 4141 + make-error: 1.3.6 4142 + 4143 + prompts@2.4.2: 4144 + dependencies: 4145 + kleur: 3.0.3 4146 + sisteransi: 1.0.5 4147 + 4148 + proto-list@1.2.4: {} 4149 + 4150 + publish-browser-extension@3.0.2: 4151 + dependencies: 4152 + cac: 6.7.14 4153 + cli-highlight: 2.1.11 4154 + consola: 3.4.2 4155 + dotenv: 16.6.1 4156 + extract-zip: 2.0.1 4157 + formdata-node: 6.0.3 4158 + listr2: 8.3.3 4159 + lodash.camelcase: 4.3.0 4160 + lodash.kebabcase: 4.1.1 4161 + lodash.snakecase: 4.1.1 4162 + ofetch: 1.4.1 4163 + open: 10.2.0 4164 + ora: 6.3.1 4165 + prompts: 2.4.2 4166 + zod: 3.25.76 4167 + transitivePeerDependencies: 4168 + - supports-color 4169 + 4170 + pump@3.0.3: 4171 + dependencies: 4172 + end-of-stream: 1.4.5 4173 + once: 1.4.0 4174 + 4175 + pupa@3.1.0: 4176 + dependencies: 4177 + escape-goat: 4.0.0 4178 + 4179 + quansync@0.2.11: {} 4180 + 4181 + queue-microtask@1.2.3: {} 4182 + 4183 + quick-format-unescaped@4.0.4: {} 4184 + 4185 + rc9@2.1.2: 4186 + dependencies: 4187 + defu: 6.1.4 4188 + destr: 2.0.5 4189 + 4190 + rc@1.2.8: 4191 + dependencies: 4192 + deep-extend: 0.6.0 4193 + ini: 1.3.8 4194 + minimist: 1.2.8 4195 + strip-json-comments: 2.0.1 4196 + 4197 + react-dom@19.1.1(react@19.1.1): 4198 + dependencies: 4199 + react: 19.1.1 4200 + scheduler: 0.26.0 4201 + 4202 + react-icons@5.5.0(react@19.1.1): 4203 + dependencies: 4204 + react: 19.1.1 4205 + 4206 + react-refresh@0.17.0: {} 4207 + 4208 + react@19.1.1: {} 4209 + 4210 + read-cache@1.0.0: 4211 + dependencies: 4212 + pify: 2.3.0 4213 + 4214 + readable-stream@2.3.8: 4215 + dependencies: 4216 + core-util-is: 1.0.3 4217 + inherits: 2.0.4 4218 + isarray: 1.0.0 4219 + process-nextick-args: 2.0.1 4220 + safe-buffer: 5.1.2 4221 + string_decoder: 1.1.1 4222 + util-deprecate: 1.0.2 4223 + 4224 + readable-stream@3.6.2: 4225 + dependencies: 4226 + inherits: 2.0.4 4227 + string_decoder: 1.3.0 4228 + util-deprecate: 1.0.2 4229 + 4230 + readdirp@3.6.0: 4231 + dependencies: 4232 + picomatch: 2.3.1 4233 + 4234 + readdirp@4.1.2: {} 4235 + 4236 + real-require@0.2.0: {} 4237 + 4238 + registry-auth-token@5.1.0: 4239 + dependencies: 4240 + '@pnpm/npm-conf': 2.3.1 4241 + 4242 + registry-url@6.0.1: 4243 + dependencies: 4244 + rc: 1.2.8 4245 + 4246 + require-directory@2.1.1: {} 4247 + 4248 + resolve@1.22.10: 4249 + dependencies: 4250 + is-core-module: 2.16.1 4251 + path-parse: 1.0.7 4252 + supports-preserve-symlinks-flag: 1.0.0 4253 + 4254 + restore-cursor@4.0.0: 4255 + dependencies: 4256 + onetime: 5.1.2 4257 + signal-exit: 3.0.7 4258 + 4259 + restore-cursor@5.1.0: 4260 + dependencies: 4261 + onetime: 7.0.0 4262 + signal-exit: 4.1.0 4263 + 4264 + reusify@1.1.0: {} 4265 + 4266 + rfdc@1.4.1: {} 4267 + 4268 + rollup@4.50.1: 4269 + dependencies: 4270 + '@types/estree': 1.0.8 4271 + optionalDependencies: 4272 + '@rollup/rollup-android-arm-eabi': 4.50.1 4273 + '@rollup/rollup-android-arm64': 4.50.1 4274 + '@rollup/rollup-darwin-arm64': 4.50.1 4275 + '@rollup/rollup-darwin-x64': 4.50.1 4276 + '@rollup/rollup-freebsd-arm64': 4.50.1 4277 + '@rollup/rollup-freebsd-x64': 4.50.1 4278 + '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 4279 + '@rollup/rollup-linux-arm-musleabihf': 4.50.1 4280 + '@rollup/rollup-linux-arm64-gnu': 4.50.1 4281 + '@rollup/rollup-linux-arm64-musl': 4.50.1 4282 + '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 4283 + '@rollup/rollup-linux-ppc64-gnu': 4.50.1 4284 + '@rollup/rollup-linux-riscv64-gnu': 4.50.1 4285 + '@rollup/rollup-linux-riscv64-musl': 4.50.1 4286 + '@rollup/rollup-linux-s390x-gnu': 4.50.1 4287 + '@rollup/rollup-linux-x64-gnu': 4.50.1 4288 + '@rollup/rollup-linux-x64-musl': 4.50.1 4289 + '@rollup/rollup-openharmony-arm64': 4.50.1 4290 + '@rollup/rollup-win32-arm64-msvc': 4.50.1 4291 + '@rollup/rollup-win32-ia32-msvc': 4.50.1 4292 + '@rollup/rollup-win32-x64-msvc': 4.50.1 4293 + fsevents: 2.3.3 4294 + 4295 + run-applescript@7.1.0: {} 4296 + 4297 + run-parallel@1.2.0: 4298 + dependencies: 4299 + queue-microtask: 1.2.3 4300 + 4301 + safe-buffer@5.1.2: {} 4302 + 4303 + safe-buffer@5.2.1: {} 4304 + 4305 + safe-stable-stringify@2.5.0: {} 4306 + 4307 + sax@1.4.1: {} 4308 + 4309 + scheduler@0.26.0: {} 4310 + 4311 + scule@1.3.0: {} 4312 + 4313 + semver@6.3.1: {} 4314 + 4315 + semver@7.7.2: {} 4316 + 4317 + set-value@4.1.0: 4318 + dependencies: 4319 + is-plain-object: 2.0.4 4320 + is-primitive: 3.0.1 4321 + 4322 + setimmediate@1.0.5: {} 4323 + 4324 + sharp@0.34.3: 4325 + dependencies: 4326 + color: 4.2.3 4327 + detect-libc: 2.0.4 4328 + semver: 7.7.2 4329 + optionalDependencies: 4330 + '@img/sharp-darwin-arm64': 0.34.3 4331 + '@img/sharp-darwin-x64': 0.34.3 4332 + '@img/sharp-libvips-darwin-arm64': 1.2.0 4333 + '@img/sharp-libvips-darwin-x64': 1.2.0 4334 + '@img/sharp-libvips-linux-arm': 1.2.0 4335 + '@img/sharp-libvips-linux-arm64': 1.2.0 4336 + '@img/sharp-libvips-linux-ppc64': 1.2.0 4337 + '@img/sharp-libvips-linux-s390x': 1.2.0 4338 + '@img/sharp-libvips-linux-x64': 1.2.0 4339 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 4340 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 4341 + '@img/sharp-linux-arm': 0.34.3 4342 + '@img/sharp-linux-arm64': 0.34.3 4343 + '@img/sharp-linux-ppc64': 0.34.3 4344 + '@img/sharp-linux-s390x': 0.34.3 4345 + '@img/sharp-linux-x64': 0.34.3 4346 + '@img/sharp-linuxmusl-arm64': 0.34.3 4347 + '@img/sharp-linuxmusl-x64': 0.34.3 4348 + '@img/sharp-wasm32': 0.34.3 4349 + '@img/sharp-win32-arm64': 0.34.3 4350 + '@img/sharp-win32-ia32': 0.34.3 4351 + '@img/sharp-win32-x64': 0.34.3 4352 + 4353 + shebang-command@2.0.0: 4354 + dependencies: 4355 + shebang-regex: 3.0.0 4356 + 4357 + shebang-regex@3.0.0: {} 4358 + 4359 + shell-quote@1.7.3: {} 4360 + 4361 + shellwords@0.1.1: {} 4362 + 4363 + signal-exit@3.0.7: {} 4364 + 4365 + signal-exit@4.1.0: {} 4366 + 4367 + simple-swizzle@0.2.2: 4368 + dependencies: 4369 + is-arrayish: 0.3.2 4370 + 4371 + sisteransi@1.0.5: {} 4372 + 4373 + slice-ansi@5.0.0: 4374 + dependencies: 4375 + ansi-styles: 6.2.3 4376 + is-fullwidth-code-point: 4.0.0 4377 + 4378 + slice-ansi@7.1.2: 4379 + dependencies: 4380 + ansi-styles: 6.2.3 4381 + is-fullwidth-code-point: 5.1.0 4382 + 4383 + sonic-boom@4.2.0: 4384 + dependencies: 4385 + atomic-sleep: 1.0.0 4386 + 4387 + source-map-js@1.2.1: {} 4388 + 4389 + source-map-support@0.5.21: 4390 + dependencies: 4391 + buffer-from: 1.1.2 4392 + source-map: 0.6.1 4393 + 4394 + source-map@0.6.1: {} 4395 + 4396 + source-map@0.7.6: {} 4397 + 4398 + spawn-sync@1.0.15: 4399 + dependencies: 4400 + concat-stream: 1.6.2 4401 + os-shim: 0.1.3 4402 + 4403 + split2@4.2.0: {} 4404 + 4405 + split@1.0.1: 4406 + dependencies: 4407 + through: 2.3.8 4408 + 4409 + stdin-discarder@0.1.0: 4410 + dependencies: 4411 + bl: 5.1.0 4412 + 4413 + stdin-discarder@0.2.2: {} 4414 + 4415 + string-width@4.2.3: 4416 + dependencies: 4417 + emoji-regex: 8.0.0 4418 + is-fullwidth-code-point: 3.0.0 4419 + strip-ansi: 6.0.1 4420 + 4421 + string-width@5.1.2: 4422 + dependencies: 4423 + eastasianwidth: 0.2.0 4424 + emoji-regex: 9.2.2 4425 + strip-ansi: 7.1.0 4426 + 4427 + string-width@7.2.0: 4428 + dependencies: 4429 + emoji-regex: 10.5.0 4430 + get-east-asian-width: 1.4.0 4431 + strip-ansi: 7.1.2 4432 + 4433 + string_decoder@1.1.1: 4434 + dependencies: 4435 + safe-buffer: 5.1.2 4436 + 4437 + string_decoder@1.3.0: 4438 + dependencies: 4439 + safe-buffer: 5.2.1 4440 + 4441 + strip-ansi@6.0.1: 4442 + dependencies: 4443 + ansi-regex: 5.0.1 4444 + 4445 + strip-ansi@7.1.0: 4446 + dependencies: 4447 + ansi-regex: 6.2.0 4448 + 4449 + strip-ansi@7.1.2: 4450 + dependencies: 4451 + ansi-regex: 6.2.2 4452 + 4453 + strip-bom@5.0.0: {} 4454 + 4455 + strip-json-comments@2.0.1: {} 4456 + 4457 + strip-json-comments@5.0.2: {} 4458 + 4459 + strip-literal@3.0.0: 4460 + dependencies: 4461 + js-tokens: 9.0.1 4462 + 4463 + stubborn-fs@1.2.5: {} 4464 + 4465 + sucrase@3.35.0: 4466 + dependencies: 4467 + '@jridgewell/gen-mapping': 0.3.13 4468 + commander: 4.1.1 4469 + glob: 10.4.5 4470 + lines-and-columns: 1.2.4 4471 + mz: 2.7.0 4472 + pirates: 4.0.7 4473 + ts-interface-checker: 0.1.13 4474 + 4475 + supports-color@7.2.0: 4476 + dependencies: 4477 + has-flag: 4.0.0 4478 + 4479 + supports-preserve-symlinks-flag@1.0.0: {} 4480 + 4481 + tailwindcss@3.4.1: 4482 + dependencies: 4483 + '@alloc/quick-lru': 5.2.0 4484 + arg: 5.0.2 4485 + chokidar: 3.6.0 4486 + didyoumean: 1.2.2 4487 + dlv: 1.1.3 4488 + fast-glob: 3.3.3 4489 + glob-parent: 6.0.2 4490 + is-glob: 4.0.3 4491 + jiti: 1.21.7 4492 + lilconfig: 2.1.0 4493 + micromatch: 4.0.8 4494 + normalize-path: 3.0.0 4495 + object-hash: 3.0.0 4496 + picocolors: 1.1.1 4497 + postcss: 8.5.6 4498 + postcss-import: 15.1.0(postcss@8.5.6) 4499 + postcss-js: 4.0.1(postcss@8.5.6) 4500 + postcss-load-config: 4.0.2(postcss@8.5.6) 4501 + postcss-nested: 6.2.0(postcss@8.5.6) 4502 + postcss-selector-parser: 6.1.2 4503 + resolve: 1.22.10 4504 + sucrase: 3.35.0 4505 + transitivePeerDependencies: 4506 + - ts-node 4507 + 4508 + thenify-all@1.6.0: 4509 + dependencies: 4510 + thenify: 3.3.1 4511 + 4512 + thenify@3.3.1: 4513 + dependencies: 4514 + any-promise: 1.3.0 4515 + 4516 + thread-stream@3.1.0: 4517 + dependencies: 4518 + real-require: 0.2.0 4519 + 4520 + through@2.3.8: {} 4521 + 4522 + tinyexec@1.0.1: {} 4523 + 4524 + tinyglobby@0.2.15: 4525 + dependencies: 4526 + fdir: 6.5.0(picomatch@4.0.3) 4527 + picomatch: 4.0.3 4528 + 4529 + tmp@0.2.5: {} 4530 + 4531 + to-regex-range@5.0.1: 4532 + dependencies: 4533 + is-number: 7.0.0 4534 + 4535 + ts-interface-checker@0.1.13: {} 4536 + 4537 + tslib@2.8.1: {} 4538 + 4539 + type-fest@3.13.1: {} 4540 + 4541 + type-fest@4.41.0: {} 4542 + 4543 + typedarray@0.0.6: {} 4544 + 4545 + typescript@5.9.2: {} 4546 + 4547 + ufo@1.6.1: {} 4548 + 4549 + uhyphen@0.2.0: {} 4550 + 4551 + undici-types@7.10.0: {} 4552 + 4553 + unimport@5.2.0: 4554 + dependencies: 4555 + acorn: 8.15.0 4556 + escape-string-regexp: 5.0.0 4557 + estree-walker: 3.0.3 4558 + local-pkg: 1.1.2 4559 + magic-string: 0.30.19 4560 + mlly: 1.8.0 4561 + pathe: 2.0.3 4562 + picomatch: 4.0.3 4563 + pkg-types: 2.3.0 4564 + scule: 1.3.0 4565 + strip-literal: 3.0.0 4566 + tinyglobby: 0.2.15 4567 + unplugin: 2.3.10 4568 + unplugin-utils: 0.2.5 4569 + 4570 + universalify@2.0.1: {} 4571 + 4572 + unplugin-utils@0.2.5: 4573 + dependencies: 4574 + pathe: 2.0.3 4575 + picomatch: 4.0.3 4576 + 4577 + unplugin@2.3.10: 4578 + dependencies: 4579 + '@jridgewell/remapping': 2.3.5 4580 + acorn: 8.15.0 4581 + picomatch: 4.0.3 4582 + webpack-virtual-modules: 0.6.2 4583 + 4584 + update-browserslist-db@1.1.3(browserslist@4.25.3): 4585 + dependencies: 4586 + browserslist: 4.25.3 4587 + escalade: 3.2.0 4588 + picocolors: 1.1.1 4589 + 4590 + update-browserslist-db@1.1.3(browserslist@4.26.0): 4591 + dependencies: 4592 + browserslist: 4.26.0 4593 + escalade: 3.2.0 4594 + picocolors: 1.1.1 4595 + 4596 + update-notifier@7.3.1: 4597 + dependencies: 4598 + boxen: 8.0.1 4599 + chalk: 5.6.2 4600 + configstore: 7.0.0 4601 + is-in-ci: 1.0.0 4602 + is-installed-globally: 1.0.0 4603 + is-npm: 6.1.0 4604 + latest-version: 9.0.0 4605 + pupa: 3.1.0 4606 + semver: 7.7.2 4607 + xdg-basedir: 5.1.0 4608 + 4609 + util-deprecate@1.0.2: {} 4610 + 4611 + uuid@8.3.2: {} 4612 + 4613 + vite-node@3.2.4(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1): 4614 + dependencies: 4615 + cac: 6.7.14 4616 + debug: 4.4.1 4617 + es-module-lexer: 1.7.0 4618 + pathe: 2.0.3 4619 + vite: 7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1) 4620 + transitivePeerDependencies: 4621 + - '@types/node' 4622 + - jiti 4623 + - less 4624 + - lightningcss 4625 + - sass 4626 + - sass-embedded 4627 + - stylus 4628 + - sugarss 4629 + - supports-color 4630 + - terser 4631 + - tsx 4632 + - yaml 4633 + 4634 + vite@7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1): 4635 + dependencies: 4636 + esbuild: 0.25.9 4637 + fdir: 6.5.0(picomatch@4.0.3) 4638 + picomatch: 4.0.3 4639 + postcss: 8.5.6 4640 + rollup: 4.50.1 4641 + tinyglobby: 0.2.15 4642 + optionalDependencies: 4643 + '@types/node': 24.3.2 4644 + fsevents: 2.3.3 4645 + jiti: 2.5.1 4646 + yaml: 2.8.1 4647 + 4648 + watchpack@2.4.4: 4649 + dependencies: 4650 + glob-to-regexp: 0.4.1 4651 + graceful-fs: 4.2.11 4652 + 4653 + wcwidth@1.0.1: 4654 + dependencies: 4655 + defaults: 1.0.4 4656 + 4657 + web-ext-run@0.2.4: 4658 + dependencies: 4659 + '@babel/runtime': 7.28.2 4660 + '@devicefarmer/adbkit': 3.3.8 4661 + chrome-launcher: 1.2.0 4662 + debounce: 1.2.1 4663 + es6-error: 4.1.1 4664 + firefox-profile: 4.7.0 4665 + fx-runner: 1.4.0 4666 + multimatch: 6.0.0 4667 + node-notifier: 10.0.1 4668 + parse-json: 7.1.1 4669 + pino: 9.7.0 4670 + promise-toolbox: 0.21.0 4671 + set-value: 4.1.0 4672 + source-map-support: 0.5.21 4673 + strip-bom: 5.0.0 4674 + strip-json-comments: 5.0.2 4675 + tmp: 0.2.5 4676 + update-notifier: 7.3.1 4677 + watchpack: 2.4.4 4678 + zip-dir: 2.0.0 4679 + transitivePeerDependencies: 4680 + - supports-color 4681 + 4682 + webpack-virtual-modules@0.6.2: {} 4683 + 4684 + when-exit@2.1.4: {} 4685 + 4686 + when@3.7.7: {} 4687 + 4688 + which@1.2.4: 4689 + dependencies: 4690 + is-absolute: 0.1.7 4691 + isexe: 1.1.2 4692 + 4693 + which@2.0.2: 4694 + dependencies: 4695 + isexe: 2.0.0 4696 + 4697 + widest-line@5.0.0: 4698 + dependencies: 4699 + string-width: 7.2.0 4700 + 4701 + winreg@0.0.12: {} 4702 + 4703 + wrap-ansi@7.0.0: 4704 + dependencies: 4705 + ansi-styles: 4.3.0 4706 + string-width: 4.2.3 4707 + strip-ansi: 6.0.1 4708 + 4709 + wrap-ansi@8.1.0: 4710 + dependencies: 4711 + ansi-styles: 6.2.1 4712 + string-width: 5.1.2 4713 + strip-ansi: 7.1.0 4714 + 4715 + wrap-ansi@9.0.2: 4716 + dependencies: 4717 + ansi-styles: 6.2.3 4718 + string-width: 7.2.0 4719 + strip-ansi: 7.1.2 4720 + 4721 + wrappy@1.0.2: {} 4722 + 4723 + wsl-utils@0.1.0: 4724 + dependencies: 4725 + is-wsl: 3.1.0 4726 + 4727 + wxt@0.20.11(@types/node@24.3.2)(jiti@2.5.1)(rollup@4.50.1)(yaml@2.8.1): 4728 + dependencies: 4729 + '@1natsu/wait-element': 4.1.2 4730 + '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.50.1) 4731 + '@webext-core/fake-browser': 1.3.2 4732 + '@webext-core/isolated-element': 1.1.2 4733 + '@webext-core/match-patterns': 1.0.3 4734 + '@wxt-dev/browser': 0.1.4 4735 + '@wxt-dev/storage': 1.2.0 4736 + async-mutex: 0.5.0 4737 + c12: 3.2.0(magicast@0.3.5) 4738 + cac: 6.7.14 4739 + chokidar: 4.0.3 4740 + ci-info: 4.3.0 4741 + consola: 3.4.2 4742 + defu: 6.1.4 4743 + dotenv: 17.2.2 4744 + dotenv-expand: 12.0.3 4745 + esbuild: 0.25.9 4746 + fast-glob: 3.3.3 4747 + filesize: 11.0.2 4748 + fs-extra: 11.3.1 4749 + get-port-please: 3.2.0 4750 + giget: 2.0.0 4751 + hookable: 5.5.3 4752 + import-meta-resolve: 4.2.0 4753 + is-wsl: 3.1.0 4754 + json5: 2.2.3 4755 + jszip: 3.10.1 4756 + linkedom: 0.18.12 4757 + magicast: 0.3.5 4758 + minimatch: 10.0.3 4759 + nano-spawn: 1.0.3 4760 + normalize-path: 3.0.0 4761 + nypm: 0.6.1 4762 + ohash: 2.0.11 4763 + open: 10.2.0 4764 + ora: 8.2.0 4765 + perfect-debounce: 2.0.0 4766 + picocolors: 1.1.1 4767 + prompts: 2.4.2 4768 + publish-browser-extension: 3.0.2 4769 + scule: 1.3.0 4770 + unimport: 5.2.0 4771 + vite: 7.1.5(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1) 4772 + vite-node: 3.2.4(@types/node@24.3.2)(jiti@2.5.1)(yaml@2.8.1) 4773 + web-ext-run: 0.2.4 4774 + transitivePeerDependencies: 4775 + - '@types/node' 4776 + - canvas 4777 + - jiti 4778 + - less 4779 + - lightningcss 4780 + - rollup 4781 + - sass 4782 + - sass-embedded 4783 + - stylus 4784 + - sugarss 4785 + - supports-color 4786 + - terser 4787 + - tsx 4788 + - yaml 4789 + 4790 + xdg-basedir@5.1.0: {} 4791 + 4792 + xml2js@0.6.2: 4793 + dependencies: 4794 + sax: 1.4.1 4795 + xmlbuilder: 11.0.1 4796 + 4797 + xmlbuilder@11.0.1: {} 4798 + 4799 + y18n@5.0.8: {} 4800 + 4801 + yallist@3.1.1: {} 4802 + 4803 + yaml@2.8.1: {} 4804 + 4805 + yargs-parser@20.2.9: {} 4806 + 4807 + yargs-parser@21.1.1: {} 4808 + 4809 + yargs@16.2.0: 4810 + dependencies: 4811 + cliui: 7.0.4 4812 + escalade: 3.2.0 4813 + get-caller-file: 2.0.5 4814 + require-directory: 2.1.1 4815 + string-width: 4.2.3 4816 + y18n: 5.0.8 4817 + yargs-parser: 20.2.9 4818 + 4819 + yargs@17.7.2: 4820 + dependencies: 4821 + cliui: 8.0.1 4822 + escalade: 3.2.0 4823 + get-caller-file: 2.0.5 4824 + require-directory: 2.1.1 4825 + string-width: 4.2.3 4826 + y18n: 5.0.8 4827 + yargs-parser: 21.1.1 4828 + 4829 + yauzl@2.10.0: 4830 + dependencies: 4831 + buffer-crc32: 0.2.13 4832 + fd-slicer: 1.1.0 4833 + 4834 + zip-dir@2.0.0: 4835 + dependencies: 4836 + async: 3.2.6 4837 + jszip: 3.10.1 4838 + 4839 + zod@3.25.76: {}
+4
pnpm-workspace.yaml
···
··· 1 + onlyBuiltDependencies: 2 + - esbuild 3 + - sharp 4 + - spawn-sync
+6
postcss.config.js
···
··· 1 + export default { 2 + plugins: { 3 + tailwindcss: {}, 4 + autoprefixer: {}, 5 + }, 6 + }
+34
tailwind.config.ts
···
··· 1 + import type { Config } from 'tailwindcss'; 2 + 3 + export default { 4 + content: ['./entrypoints/popup/**/*.{js,ts,jsx,tsx}'], 5 + theme: { 6 + colors: { 7 + accent: 'rgb(63, 93, 179)', 8 + background: 'rgb(31, 30, 30)', 9 + border: 'rgb(68, 67, 66)', 10 + header: 'rgba(24, 23, 23)', 11 + input: 'rgb(31, 30, 30)', 12 + item: 'rgb(46, 45, 45)', 13 + subtext: 'rgb(175, 172, 171)', 14 + text: 'rgb(247, 245, 244)', 15 + 16 + warning: 'rgb(229, 153, 62)', 17 + danger: '#ef4444', 18 + success: 'rgb(30, 166, 114)', 19 + 20 + pink: '#f5c2e7', 21 + }, 22 + extend: { 23 + animation: { 24 + 'spin-fast': 'spin 1s linear infinite', 25 + }, 26 + keyframes: { 27 + spin: { 28 + to: { transform: 'rotate(360deg)' }, 29 + }, 30 + }, 31 + }, 32 + }, 33 + plugins: [], 34 + } satisfies Config;
+7
tsconfig.json
···
··· 1 + { 2 + "extends": "./.wxt/tsconfig.json", 3 + "compilerOptions": { 4 + "allowImportingTsExtensions": true, 5 + "jsx": "react-jsx" 6 + } 7 + }
+16
types/eligibility.ts
···
··· 1 + export interface Eligibility { 2 + eligible: boolean; 3 + reason: string; 4 + id?: string; 5 + checkedAt?: number; 6 + error?: string; 7 + offerType?: string; 8 + } 9 + 10 + export interface EligibilityApiResponse { 11 + error?: string; 12 + data?: { 13 + offerType?: string; 14 + tcds?: Array<{ tcd: string; token: string }>; 15 + }; 16 + }
+10
types/messages.ts
···
··· 1 + // Message types for browser.runtime messages 2 + 3 + export type RuntimeMessage< 4 + T extends Record<string, unknown> = Record<string, unknown>, 5 + > = { action: string } & T; 6 + 7 + export interface Message { 8 + action: string; 9 + [key: string]: unknown; 10 + }
+7
types/storage.ts
···
··· 1 + export interface DefaultStorageOptions { 2 + useSystemPush?: boolean; 3 + useNtfyPush?: boolean; 4 + ntfyUrl?: string; 5 + ntfyTopic?: string; 6 + ntfyToken?: string; 7 + }
+15
types/tokens.ts
···
··· 1 + import type { DefaultStorageOptions } from '$types/storage'; 2 + 3 + export interface Token { 4 + tcd: string; 5 + token: string; 6 + timestamp?: number; 7 + } 8 + 9 + export interface Offer { 10 + tcds: Token[]; 11 + } 12 + 13 + export interface StoreTokensResult extends DefaultStorageOptions { 14 + tailscaleTokens?: Token[]; 15 + }
+38
wxt.config.ts
···
··· 1 + import { resolve } from 'node:path'; 2 + import { defineConfig } from 'wxt'; 3 + 4 + // See https://wxt.dev/api/config.html 5 + export default defineConfig({ 6 + modules: ['@wxt-dev/module-react', '@wxt-dev/auto-icons'], 7 + manifest: { 8 + name: 'tailname', 9 + description: 'Search for custom tailnet name offers with keywords.', 10 + version: '1.0', 11 + permissions: [ 12 + 'cookies', 13 + 'alarms', 14 + 'storage', 15 + 'notifications', 16 + 'scripting', 17 + 'tabs', 18 + ], 19 + host_permissions: ['https://login.tailscale.com/*'], 20 + incognito: import.meta.env.CHROME ? 'split' : 'spanning', 21 + browser_specific_settings: { 22 + gecko: { 23 + id: 'tailname@sapphic.moe', 24 + }, 25 + }, 26 + }, 27 + alias: { 28 + $background: resolve('entrypoints/background'), 29 + $components: resolve('entrypoints/popup/components'), 30 + $config: resolve('config.ts'), 31 + $handlers: resolve('handlers'), 32 + $helpers: resolve('helpers'), 33 + $hooks: resolve('entrypoints/popup/hooks'), 34 + $popup: resolve('entrypoints/popup'), 35 + $screens: resolve('entrypoints/popup/screens'), 36 + $types: resolve('types'), 37 + }, 38 + });