├── .gitignore ├── postcss.config.js ├── public ├── icons │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ └── icon96.png ├── manifest.json ├── popup.html └── options.html ├── src ├── utils │ └── tabs.ts ├── popup.ts ├── actions │ ├── closeBlankPages.ts │ ├── deduplicateTabs.ts │ ├── groupDomains.ts │ ├── organizeViaAi.ts │ └── sortTabs.ts ├── main.css ├── background.ts ├── options.ts ├── categories.ts └── openai.ts ├── .editorconfig ├── tailwind.config.js ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md ├── tsup.config.ts ├── .github └── workflows │ └── release.yml └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | /node_modules 5 | /dist 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceemotion/ai-tab-organizer/HEAD/public/icons/icon128.png -------------------------------------------------------------------------------- /public/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceemotion/ai-tab-organizer/HEAD/public/icons/icon16.png -------------------------------------------------------------------------------- /public/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceemotion/ai-tab-organizer/HEAD/public/icons/icon32.png -------------------------------------------------------------------------------- /public/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceemotion/ai-tab-organizer/HEAD/public/icons/icon48.png -------------------------------------------------------------------------------- /public/icons/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceemotion/ai-tab-organizer/HEAD/public/icons/icon96.png -------------------------------------------------------------------------------- /src/utils/tabs.ts: -------------------------------------------------------------------------------- 1 | export const mapTabIds = (tabs: { id?: number | undefined }[]) => ( 2 | tabs 3 | .map((tab) => tab.id) 4 | .filter((id): id is number => !!id) 5 | ); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{js,ts,vue,css,html}] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './public/*.html', 5 | ], 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | 'sans': ['Inter', 'sans-serif'], 10 | }, 11 | }, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /src/popup.ts: -------------------------------------------------------------------------------- 1 | const registerAction = (id: string) => { 2 | document.getElementById(id)?.addEventListener('click', () => { 3 | chrome.runtime.sendMessage({ action: id }); 4 | }); 5 | }; 6 | 7 | registerAction('action-ai'); 8 | registerAction('action-deduplicate'); 9 | registerAction('action-closeblanks'); 10 | registerAction('action-sort'); 11 | registerAction('action-group-tlds'); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "module": "esnext", 5 | "target": "ES2022", 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "noEmit": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "moduleResolution": "node", 13 | "baseUrl": ".", 14 | }, 15 | "exclude": ["node_modules", "build", "dist"] 16 | } 17 | -------------------------------------------------------------------------------- /src/actions/closeBlankPages.ts: -------------------------------------------------------------------------------- 1 | import { mapTabIds } from "../utils/tabs"; 2 | 3 | export default async function closeBlankPages() { 4 | const tabs = await chrome.tabs.query({}); 5 | const blankTabs = tabs.filter((tab) => ( 6 | tab.url === 'chrome://newtab/' || 7 | tab.url === 'about:blank' || 8 | tab.url === 'edge://newtab/' 9 | )); 10 | 11 | console.log( 12 | `Found ${blankTabs.length} blank tabs, closing`, 13 | blankTabs.map((tab) => tab.url), 14 | ); 15 | 16 | chrome.tabs.remove(mapTabIds(blankTabs)); 17 | } 18 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .form-button { 7 | @apply px-4 py-2 text-sm font-medium text-white bg-zinc-700 border border-zinc-800 rounded shadow hover:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-zinc-500; 8 | } 9 | 10 | .form-input { 11 | @apply block w-full px-3 py-2 text-sm border bg-white border-zinc-400 rounded shadow-sm focus:outline-none focus:ring-zinc-500 focus:border-zinc-500; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-tab-organizer", 3 | "version": "1.4", 4 | "scripts": { 5 | "build": "tsup", 6 | "watch": "tsup -- --dev" 7 | }, 8 | "dependencies": { 9 | "ky": "^0.33.3", 10 | "lodash-es": "^4.17.21", 11 | "zod": "^3.21.4", 12 | "zod-to-json-schema": "^3.21.4" 13 | }, 14 | "devDependencies": { 15 | "@types/chrome": "^0.0.243", 16 | "@types/lodash-es": "^4.17.8", 17 | "@types/node": "^20.4.8", 18 | "tailwindcss": "^3.3.3", 19 | "tsup": "^7.2.0", 20 | "typescript": "^5.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/actions/deduplicateTabs.ts: -------------------------------------------------------------------------------- 1 | import { mapTabIds } from "../utils/tabs"; 2 | 3 | // Keep these URLs open, as they don't keep their state in the URl 4 | const keepUrlsRegex = /(chat\.openai\.com)/i; 5 | 6 | export default async function deduplicateTabs() { 7 | const tabs = await chrome.tabs.query({}); 8 | const urls = tabs.map((tab) => tab.url); 9 | 10 | const duplicates = urls.filter((url, index) => urls.indexOf(url) !== index); 11 | const duplicateTabs = tabs.filter((tab) => ( 12 | duplicates.includes(tab.url) && !keepUrlsRegex.test(tab.url!) 13 | )); 14 | 15 | console.log( 16 | `Found ${duplicateTabs.length} duplicate tabs, closing`, 17 | duplicateTabs.map((tab) => tab.url), 18 | ); 19 | 20 | chrome.tabs.remove(mapTabIds(duplicateTabs)); 21 | } 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "manifest_version": 3, 4 | "author": "spaceemotion", 5 | "name": "AI Tab Organizer", 6 | "description": "Use AI to organize all your tabs into categorized groups!", 7 | "homepage_url": "https://github.com/spaceemotion/ai-tab-organizer", 8 | "version": "1.4", 9 | "permissions": [ 10 | "tabs", 11 | "tabGroups", 12 | "storage" 13 | ], 14 | "incognito": "split", 15 | "background": { 16 | "service_worker": "background.js" 17 | }, 18 | "action": { 19 | "default_popup": "popup.html" 20 | }, 21 | "options_ui": { 22 | "page": "options.html", 23 | "open_in_tab": false 24 | }, 25 | "host_permissions": ["https://api.openai.com/*"], 26 | "icons": { 27 | "16": "icons/icon16.png", 28 | "32": "icons/icon32.png", 29 | "48": "icons/icon48.png", 30 | "128": "icons/icon128.png" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AI Tab Organizer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/background.ts: -------------------------------------------------------------------------------- 1 | 2 | import deduplicateTabs from './actions/deduplicateTabs'; 3 | import closeBlankPages from './actions/closeBlankPages'; 4 | import organizeViaAi from './actions/organizeViaAi'; 5 | import sortTabs from './actions/sortTabs'; 6 | import groupDomains from './actions/groupDomains'; 7 | 8 | enum Actions { 9 | CloseBlanks = 'action-closeblanks', 10 | Deduplicate = 'action-deduplicate', 11 | Ai = 'action-ai', 12 | Sort = 'action-sort', 13 | GroupTLDs = 'action-group-tlds', 14 | } 15 | 16 | const actionMap: Record = { 17 | [Actions.CloseBlanks]: closeBlankPages, 18 | [Actions.Deduplicate]: deduplicateTabs, 19 | [Actions.Ai]: organizeViaAi, 20 | [Actions.Sort]: sortTabs, 21 | [Actions.GroupTLDs]: groupDomains, 22 | }; 23 | 24 | chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => { 25 | if (!message.action) { 26 | return; 27 | } 28 | 29 | await actionMap[message.action as Actions]?.(); 30 | }); 31 | -------------------------------------------------------------------------------- /public/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AI Tab Organizer Extension Options 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 20 | 21 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | const getAPIKeyElement = () => document.getElementById('api-key') as HTMLInputElement; 2 | const getAIModelElement = () => document.getElementById('ai-model') as HTMLInputElement; 3 | 4 | // Saves options to chrome.storage 5 | const saveOptions = async () => { 6 | const key = getAPIKeyElement().value; 7 | const model = getAIModelElement().value; 8 | 9 | await chrome.storage.sync.set({ 10 | apiKey: key, 11 | aiModel: model, 12 | }); 13 | }; 14 | 15 | // Restores select box and checkbox state using the preferences 16 | // stored in chrome.storage. 17 | const restoreOptions = async () => { 18 | const options = await chrome.storage.sync.get({ 19 | apiKey: '', 20 | aiModel: '', 21 | }); 22 | 23 | getAPIKeyElement().value = options.apiKey; 24 | getAIModelElement().value = options.aiModel; 25 | }; 26 | 27 | document.addEventListener('DOMContentLoaded', restoreOptions); 28 | document.getElementById('save')?.addEventListener('click', saveOptions); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 spaceemotion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](./public/icons/icon96.png) 2 | 3 | # AI Tab Organizer 4 | Organizes all tabs across windows into AI-generated groups. Uses your own OpenAI API. 5 | 6 | > [!NOTE] 7 | > **This is a fun, working experiment with Chrome and OpenAI.** 8 | > Not only is the OpenAI API kind of slow, it sometimes groups tabs in a weird way. 9 | > Use at your own risk. 10 | 11 | ## Features 12 | - **AI Organize:** Organizes all tabs across windows into AI-generated groups 13 | - **Deduplicate Tabs:** Closes all duplicate tabs 14 | - **Close Blank Pages:** Closes all tabs that are blank pages (e.g. `about:blank`) 15 | - **Sort Tabs by URL:** Sorts all tabs by URL, even inside groups 16 | - **Group Tabs by URL:** Groups all tabs by hostname when they have a minimum of 10 tabs 17 | 18 | ## Local Install 19 | 1. Download `.zip` file from https://github.com/spaceemotion/ai-tab-organizer/releases/latest 20 | 2. Unpack to a folder of your choosing 21 | 3. Enable developer mode in Chrome/Edge 22 | 4. Load from unpacked extension 23 | 24 | ## Settings 25 | Open up the extension settings to enter your [OpenAI API Key] 26 | or specify the [AI model] to be used. 27 | 28 | [OpenAI API Key]: "Generate a new API token" 29 | [AI model]: "View all available models" 30 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync } from "node:fs"; 3 | import { join } from 'node:path'; 4 | 5 | const isDev = process.argv.includes('--dev'); 6 | 7 | function copyRecursiveSync(src: string, dest: string) { 8 | const exists = existsSync(src); 9 | const stats = exists && statSync(src); 10 | const isDirectory = stats && stats.isDirectory(); 11 | 12 | if (exists && isDirectory) { 13 | mkdirSync(dest, { recursive: true }); 14 | readdirSync(src).forEach(childItemName => { 15 | copyRecursiveSync(join(src, childItemName), join(dest, childItemName)); 16 | }); 17 | } else { 18 | copyFileSync(src, dest); 19 | } 20 | } 21 | 22 | export default defineConfig({ 23 | entry: [ 24 | 'src/background.ts', 25 | 'src/popup.ts', 26 | 'src/options.ts', 27 | 'src/main.css', 28 | ], 29 | 30 | watch: isDev, 31 | 32 | publicDir: 'public', 33 | 34 | noExternal: [ 35 | 'zod', 36 | 'zod-to-json-schema', 37 | 'lodash-es', 38 | 'ky', 39 | ], 40 | 41 | platform: 'browser', 42 | 43 | treeshake: true, 44 | splitting: false, 45 | clean: true, 46 | 47 | sourcemap: false, 48 | minify: !isDev, 49 | 50 | 51 | // WORKAROUND for https://github.com/egoist/tsup/issues/831 52 | onSuccess: isDev ? async () => { 53 | copyRecursiveSync('public', 'dist'); 54 | } : undefined, 55 | }) 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | name: Publish release artifact 11 | runs-on: ubuntu-latest 12 | 13 | permissions: write-all 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Install Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 20 23 | 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v2 26 | with: 27 | version: 8 28 | run_install: false 29 | 30 | - name: Get pnpm store directory 31 | shell: bash 32 | run: | 33 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 34 | 35 | - uses: actions/cache@v3 36 | name: Setup pnpm cache 37 | with: 38 | path: ${{ env.STORE_PATH }} 39 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 40 | restore-keys: | 41 | ${{ runner.os }}-pnpm-store- 42 | 43 | - name: Install dependencies 44 | run: pnpm install 45 | 46 | - name: Build extension 47 | run: pnpm run build 48 | 49 | - name: Archive Release 50 | uses: thedoctor0/zip-release@0.7.1 51 | with: 52 | type: 'zip' 53 | filename: 'release.zip' 54 | directory: 'dist' 55 | 56 | - name: Upload Release 57 | uses: ncipollo/release-action@v1.12.0 58 | with: 59 | artifacts: "dist/release.zip" 60 | generateReleaseNotes: true 61 | -------------------------------------------------------------------------------- /src/actions/groupDomains.ts: -------------------------------------------------------------------------------- 1 | import { organizeTabs } from "src/categories"; 2 | 3 | export default async function groupDomains({ 4 | minTabs = 10, 5 | }: { 6 | minTabs?: number 7 | } = {}) { 8 | // Get all tabs we care about 9 | const ungroupedTabs = await chrome.tabs.query({ 10 | pinned: false, 11 | groupId: chrome.tabGroups.TAB_GROUP_ID_NONE, 12 | }); 13 | 14 | console.log(`Found ${ungroupedTabs.length} tabs`); 15 | 16 | // Build a map of domains to tabs, group them by TLD (e.g. example.com) 17 | const domainMap = new Map(); 18 | 19 | for (const tab of ungroupedTabs) { 20 | const url = new URL(tab.url!); 21 | const domain = url.hostname 22 | .replace(/^www\./, ''); // Remove common prefixes 23 | 24 | if (!domain) { 25 | // Might be a local file 26 | continue; 27 | } 28 | 29 | if (!domainMap.has(domain)) { 30 | domainMap.set(domain, []); 31 | } 32 | 33 | domainMap.get(domain)!.push(tab); 34 | } 35 | 36 | console.log(`Found ${domainMap.size} domains`); 37 | 38 | const domains = Array.from(domainMap.entries()) 39 | // Filter out domains that don't have enough tabs 40 | .filter(([_, tabs]) => tabs.length >= minTabs) 41 | // Map to domain name and tab IDs 42 | .map(([domain, tabs]) => [domain, tabs.map((tab) => tab.id)] as [string, number[]]) 43 | // Sort by domain name 44 | .sort((a, b) => a[0].localeCompare(b[0])); 45 | 46 | console.log(`Found ${domains.length} domains with at least ${minTabs} tabs`); 47 | 48 | // Create a group for each domain, but in the current window 49 | await organizeTabs( 50 | Object.fromEntries(domains), 51 | await chrome.windows.getCurrent(), 52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/actions/organizeViaAi.ts: -------------------------------------------------------------------------------- 1 | import { chunk } from 'lodash-es'; 2 | 3 | import { collectTabs, mergeCategories, organizeTabs } from '../categories'; 4 | import { analyzeTabs } from '../openai'; 5 | 6 | export default async function organizeViaAi() { 7 | const { apiKey, aiModel } = await chrome.storage.sync.get({ 8 | apiKey: '', 9 | aiModel: '', 10 | }); 11 | 12 | if (!apiKey) { 13 | console.error('No API key found'); 14 | return; 15 | } 16 | 17 | const tabs = await collectTabs(); 18 | const chunks = chunk(tabs, 60); 19 | 20 | const categories = await Promise.all( 21 | chunks.map((chunk) => analyzeTabs({ 22 | model: aiModel, 23 | apiKey, 24 | }, chunk)), 25 | ); 26 | 27 | console.log("Got all results, now merging"); 28 | 29 | const allMatchedTabIds = categories.flatMap((category) => category.tabs); 30 | const allTabIds = tabs.map((tab) => tab.id); 31 | 32 | // Make sure the returned Tab IDs are actually valid 33 | const invalidTabIds = allMatchedTabIds.filter((id) => !allTabIds.includes(id)); 34 | 35 | if (invalidTabIds.length > 0) { 36 | console.warn(`Got invalid Tab IDs: ${invalidTabIds.join(', ')}`); 37 | categories.push({ 38 | "Other": invalidTabIds, 39 | }); 40 | } 41 | 42 | // Find out if we have any tabs that have not been categorized 43 | const leftovers = allTabIds.filter((id) => !allMatchedTabIds.includes(id)); 44 | 45 | if (leftovers.length > 0) { 46 | console.warn(`Got leftover Tab IDs: ${leftovers.join(', ')}`); 47 | categories.push({ 48 | "Other": leftovers, 49 | }); 50 | } 51 | 52 | // Merge shared categories 53 | const merged = mergeCategories(categories); 54 | console.log(merged); 55 | 56 | organizeTabs(merged); 57 | console.log("Done"); 58 | }; 59 | -------------------------------------------------------------------------------- /src/categories.ts: -------------------------------------------------------------------------------- 1 | export interface TabItem { 2 | id: number; 3 | title: string; 4 | domain: string; 5 | } 6 | 7 | const extractTabInfo = (tab: chrome.tabs.Tab): TabItem => { 8 | const url = new URL(tab.url ?? ''); 9 | 10 | return { 11 | id: tab.id ?? -1, 12 | title: tab.title ?? '', 13 | domain: url.hostname.replace(/^www\./, ''), 14 | }; 15 | }; 16 | 17 | export const collectTabs = async (): Promise => { 18 | return new Promise((resolve) => { 19 | chrome.tabs.query({}, (tabs) => { 20 | resolve(tabs.map(extractTabInfo)); 21 | }); 22 | }); 23 | }; 24 | 25 | export const mergeCategories = (categories: Record[]): Record => { 26 | const merged: Record = {}; 27 | 28 | for (const category of categories) { 29 | for (const [name, tabs] of Object.entries(category)) { 30 | const validIds = tabs.filter((id) => Number.isSafeInteger(id) && id >= 0); 31 | 32 | if (validIds.length === 0) { 33 | continue; 34 | } 35 | 36 | if (!merged[name]) { 37 | merged[name] = validIds; 38 | } else { 39 | merged[name].push(...validIds); 40 | } 41 | } 42 | } 43 | 44 | return merged; 45 | }; 46 | 47 | export const organizeTabs = async (categorizedTabs: Record, window?: chrome.windows.Window): Promise => { 48 | const newWindow = window ?? await chrome.windows.create(); 49 | 50 | await Promise.all(Object.entries(categorizedTabs).map(async ([name, tabIds]) => { 51 | // Create a new tab group for each category 52 | const group = await chrome.tabs.group({ 53 | tabIds, 54 | createProperties: { 55 | windowId: newWindow.id, 56 | }, 57 | }); 58 | 59 | // Set the title of the group to the category name 60 | await chrome.tabGroups.update(group, { 61 | title: name, 62 | collapsed: true, 63 | }); 64 | })); 65 | }; 66 | -------------------------------------------------------------------------------- /src/openai.ts: -------------------------------------------------------------------------------- 1 | import { omit } from "lodash-es"; 2 | import { z } from "zod"; 3 | import zodToJsonSchemaImpl from 'zod-to-json-schema'; 4 | import ky from 'ky'; 5 | 6 | import type { TabItem } from "./categories"; 7 | 8 | function zodToJsonSchema(schema: z.ZodType) { 9 | return omit( 10 | zodToJsonSchemaImpl(schema, { $refStrategy: 'none' }), 11 | '$ref', 12 | '$schema', 13 | 'default', 14 | 'definitions', 15 | 'description', 16 | 'markdownDescription', 17 | ); 18 | } 19 | 20 | const SetTabCategoriesSchema = z.object({ 21 | categories: z.array(z.object({ 22 | name: z.string().describe('The name or title of the category.'), 23 | tabs: z.array(z.number()).describe('A list of all Tab IDs that belong to this category.'), 24 | })), 25 | }); 26 | 27 | interface AiOptions { 28 | model?: string; 29 | apiKey: string; 30 | } 31 | 32 | export const analyzeTabs = async (options: AiOptions, tabs: TabItem[]): Promise> => { 33 | try { 34 | // send the request containing the messages to the OpenAI API 35 | const response = await ky.post('https://api.openai.com/v1/chat/completions', { 36 | retry: { 37 | limit: 3, 38 | maxRetryAfter: 500, 39 | }, 40 | timeout: 1000 * 59, // almost a minute 41 | headers: { 42 | 'Content-Type': 'application/json', 43 | 'Authorization': `Bearer ${options.apiKey}` 44 | }, 45 | json: { 46 | model: options.model ?? 'gpt-3.5-turbo', 47 | temperature: 0.7, 48 | messages: [ 49 | { 50 | "role": "system", 51 | "content": ` 52 | You are a browser tab categorizer. 53 | You will be given a list of tabs, each with a domain and their title. 54 | Organize the whole list of tabs into groups based on their topic. 55 | Weigh the tab domains more into consideration than their title. 56 | In case many tabs share the same domain, find individual topics. 57 | Do not be too broad or generic. 58 | In case a tab is too miscellaneous, use "Other". 59 | Limit yourself to six categories maximum. 60 | At least 3 tabs have to be in each category. 61 | `.trim(), 62 | }, 63 | { 64 | "role": "user", 65 | "content": JSON.stringify(tabs), 66 | } 67 | ], 68 | function_call: { 69 | name: 'set_tab_categories', 70 | }, 71 | functions: [ 72 | { 73 | name: 'set_tab_categories', 74 | description: 'Moves all tabs into given categories.', 75 | parameters: zodToJsonSchema(SetTabCategoriesSchema), 76 | }, 77 | ], 78 | }, 79 | }); 80 | 81 | // Get the data from the API response as parsed schema 82 | const data: any = await response.json(); 83 | const calledFuntionRaw = data.choices[0]?.message?.function_call?.arguments ?? ''; 84 | const calledFunction = SetTabCategoriesSchema.parse(JSON.parse(calledFuntionRaw)); 85 | 86 | // Return a mapping of category name to tab ID list 87 | return calledFunction.categories.reduce((acc, category) => { 88 | acc[category.name] = category.tabs; 89 | return acc; 90 | }, {} as Record); 91 | } catch (error) { 92 | throw error; 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/actions/sortTabs.ts: -------------------------------------------------------------------------------- 1 | import { mapTabIds } from "src/utils/tabs"; 2 | 3 | type Entry = { 4 | id: number; 5 | groupId: number; 6 | index: number; 7 | url: string; 8 | } 9 | 10 | const isGroupedTab = (id: number) => ( 11 | id !== chrome.tabGroups.TAB_GROUP_ID_NONE 12 | ); 13 | 14 | /** 15 | * Sorts tabs by their URL, using a custom strategy, 16 | * so we have the following order: 17 | * 18 | * 1. bar.com 19 | * 2. foo.com 20 | * 3. baz.foo.com 21 | * 4. example.foo.com 22 | * 5. a.sub.xyz.com 23 | * 6. b.sub.xyz.com 24 | * 25 | * Afterwards the URL gets ordered by the rest (/subdirector/file.html?query#hash) 26 | */ 27 | const urlTabSort = (a: Entry, b: Entry): number => { 28 | const aUrl = new URL(a.url); 29 | const bUrl = new URL(b.url); 30 | 31 | const aParts = aUrl.hostname.split('.').reverse(); 32 | const bParts = bUrl.hostname.split('.').reverse(); 33 | 34 | for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) { 35 | if (aParts[i] !== bParts[i]) { 36 | // If one URL has fewer parts, it goes first 37 | if (!aParts[i]) return -1; 38 | if (!bParts[i]) return 1; 39 | 40 | // Otherwise, compare the parts 41 | return aParts[i].localeCompare(bParts[i], 'en'); 42 | } 43 | } 44 | 45 | // If the hostnames are identical, sort by their full URL 46 | return aUrl.href.localeCompare(bUrl.href, 'en'); 47 | } 48 | 49 | export default async function sortTabs() { 50 | const tabs = await chrome.tabs.query({ 51 | currentWindow: true, 52 | }); 53 | 54 | // 1. Group the entries by groupId 55 | const grouped: { [groupId: number]: Entry[] } = {}; 56 | const groupOrder: number[] = []; // Keep track of the order of the groupIds 57 | 58 | for (const tab of tabs) { 59 | if (!grouped.hasOwnProperty(tab.groupId)) { 60 | grouped[tab.groupId] = []; 61 | 62 | // Remember the order of appearance of groupIds 63 | if (isGroupedTab(tab.groupId)) { 64 | groupOrder.push(tab.groupId); 65 | } 66 | } 67 | 68 | grouped[tab.groupId].push({ 69 | id: tab.id!, 70 | groupId: tab.groupId, 71 | index: tab.index, 72 | url: tab.url ?? '', 73 | }); 74 | } 75 | 76 | 77 | // 2. Sort each group individually 78 | for (let groupId in grouped) { 79 | grouped[groupId].sort(urlTabSort); 80 | console.log(`Sorted group ${groupId}`, grouped[groupId]); 81 | } 82 | 83 | // 3. Move ungrouped tabs to the end 84 | const validGroups = groupOrder.filter((groupId) => isGroupedTab(groupId)); 85 | // validGroups.push(chrome.tabGroups.TAB_GROUP_ID_NONE); 86 | 87 | // 4. Re-apply the sort in the same group order as before and 88 | // make sure that every entry has a continuous index 89 | const sortedTabIds: number[] = []; 90 | 91 | for (let groupId of groupOrder) { 92 | for (let entry of grouped[groupId]) { 93 | sortedTabIds.push(entry.id); 94 | } 95 | } 96 | 97 | // Move each tab group individually, so they are preserved 98 | // https://developer.chrome.com/docs/extensions/reference/tabs/#method-move 99 | let offset = 0; 100 | 101 | for (const groupId of validGroups) { 102 | console.log(`Moving group ${groupId}`, grouped[groupId]); 103 | const [_, ...rest] = grouped[groupId]; 104 | 105 | for (let index = 0; index < rest.length; index++) { 106 | const tab = grouped[groupId][index]; 107 | await chrome.tabs.move(tab.id, { index: offset + index }); 108 | } 109 | 110 | await chrome.tabs.group({ 111 | groupId, 112 | tabIds: mapTabIds(grouped[groupId]), 113 | }); 114 | 115 | offset += grouped[groupId].length; 116 | } 117 | 118 | // Move ungrouped tabs to the end 119 | const ungroupedIds = mapTabIds(grouped[chrome.tabGroups.TAB_GROUP_ID_NONE] ?? []); 120 | 121 | if (ungroupedIds.length > 0) { 122 | console.log('Moving ungrouped tabs', grouped[chrome.tabGroups.TAB_GROUP_ID_NONE]); 123 | await chrome.tabs.move(ungroupedIds, { index: -1 }); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | ky: 9 | specifier: ^0.33.3 10 | version: 0.33.3 11 | lodash-es: 12 | specifier: ^4.17.21 13 | version: 4.17.21 14 | zod: 15 | specifier: ^3.21.4 16 | version: 3.21.4 17 | zod-to-json-schema: 18 | specifier: ^3.21.4 19 | version: 3.21.4(zod@3.21.4) 20 | 21 | devDependencies: 22 | '@types/chrome': 23 | specifier: ^0.0.243 24 | version: 0.0.243 25 | '@types/lodash-es': 26 | specifier: ^4.17.8 27 | version: 4.17.8 28 | '@types/node': 29 | specifier: ^20.4.8 30 | version: 20.4.8 31 | tailwindcss: 32 | specifier: ^3.3.3 33 | version: 3.3.3 34 | tsup: 35 | specifier: ^7.2.0 36 | version: 7.2.0(postcss@8.4.27)(typescript@5.1.3) 37 | typescript: 38 | specifier: ^5.1.0 39 | version: 5.1.3 40 | 41 | packages: 42 | 43 | /@alloc/quick-lru@5.2.0: 44 | resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 45 | engines: {node: '>=10'} 46 | dev: true 47 | 48 | /@esbuild/android-arm64@0.18.17: 49 | resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==} 50 | engines: {node: '>=12'} 51 | cpu: [arm64] 52 | os: [android] 53 | requiresBuild: true 54 | dev: true 55 | optional: true 56 | 57 | /@esbuild/android-arm@0.18.17: 58 | resolution: {integrity: sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==} 59 | engines: {node: '>=12'} 60 | cpu: [arm] 61 | os: [android] 62 | requiresBuild: true 63 | dev: true 64 | optional: true 65 | 66 | /@esbuild/android-x64@0.18.17: 67 | resolution: {integrity: sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==} 68 | engines: {node: '>=12'} 69 | cpu: [x64] 70 | os: [android] 71 | requiresBuild: true 72 | dev: true 73 | optional: true 74 | 75 | /@esbuild/darwin-arm64@0.18.17: 76 | resolution: {integrity: sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==} 77 | engines: {node: '>=12'} 78 | cpu: [arm64] 79 | os: [darwin] 80 | requiresBuild: true 81 | dev: true 82 | optional: true 83 | 84 | /@esbuild/darwin-x64@0.18.17: 85 | resolution: {integrity: sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==} 86 | engines: {node: '>=12'} 87 | cpu: [x64] 88 | os: [darwin] 89 | requiresBuild: true 90 | dev: true 91 | optional: true 92 | 93 | /@esbuild/freebsd-arm64@0.18.17: 94 | resolution: {integrity: sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==} 95 | engines: {node: '>=12'} 96 | cpu: [arm64] 97 | os: [freebsd] 98 | requiresBuild: true 99 | dev: true 100 | optional: true 101 | 102 | /@esbuild/freebsd-x64@0.18.17: 103 | resolution: {integrity: sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==} 104 | engines: {node: '>=12'} 105 | cpu: [x64] 106 | os: [freebsd] 107 | requiresBuild: true 108 | dev: true 109 | optional: true 110 | 111 | /@esbuild/linux-arm64@0.18.17: 112 | resolution: {integrity: sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==} 113 | engines: {node: '>=12'} 114 | cpu: [arm64] 115 | os: [linux] 116 | requiresBuild: true 117 | dev: true 118 | optional: true 119 | 120 | /@esbuild/linux-arm@0.18.17: 121 | resolution: {integrity: sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==} 122 | engines: {node: '>=12'} 123 | cpu: [arm] 124 | os: [linux] 125 | requiresBuild: true 126 | dev: true 127 | optional: true 128 | 129 | /@esbuild/linux-ia32@0.18.17: 130 | resolution: {integrity: sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==} 131 | engines: {node: '>=12'} 132 | cpu: [ia32] 133 | os: [linux] 134 | requiresBuild: true 135 | dev: true 136 | optional: true 137 | 138 | /@esbuild/linux-loong64@0.18.17: 139 | resolution: {integrity: sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==} 140 | engines: {node: '>=12'} 141 | cpu: [loong64] 142 | os: [linux] 143 | requiresBuild: true 144 | dev: true 145 | optional: true 146 | 147 | /@esbuild/linux-mips64el@0.18.17: 148 | resolution: {integrity: sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==} 149 | engines: {node: '>=12'} 150 | cpu: [mips64el] 151 | os: [linux] 152 | requiresBuild: true 153 | dev: true 154 | optional: true 155 | 156 | /@esbuild/linux-ppc64@0.18.17: 157 | resolution: {integrity: sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==} 158 | engines: {node: '>=12'} 159 | cpu: [ppc64] 160 | os: [linux] 161 | requiresBuild: true 162 | dev: true 163 | optional: true 164 | 165 | /@esbuild/linux-riscv64@0.18.17: 166 | resolution: {integrity: sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==} 167 | engines: {node: '>=12'} 168 | cpu: [riscv64] 169 | os: [linux] 170 | requiresBuild: true 171 | dev: true 172 | optional: true 173 | 174 | /@esbuild/linux-s390x@0.18.17: 175 | resolution: {integrity: sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==} 176 | engines: {node: '>=12'} 177 | cpu: [s390x] 178 | os: [linux] 179 | requiresBuild: true 180 | dev: true 181 | optional: true 182 | 183 | /@esbuild/linux-x64@0.18.17: 184 | resolution: {integrity: sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==} 185 | engines: {node: '>=12'} 186 | cpu: [x64] 187 | os: [linux] 188 | requiresBuild: true 189 | dev: true 190 | optional: true 191 | 192 | /@esbuild/netbsd-x64@0.18.17: 193 | resolution: {integrity: sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==} 194 | engines: {node: '>=12'} 195 | cpu: [x64] 196 | os: [netbsd] 197 | requiresBuild: true 198 | dev: true 199 | optional: true 200 | 201 | /@esbuild/openbsd-x64@0.18.17: 202 | resolution: {integrity: sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==} 203 | engines: {node: '>=12'} 204 | cpu: [x64] 205 | os: [openbsd] 206 | requiresBuild: true 207 | dev: true 208 | optional: true 209 | 210 | /@esbuild/sunos-x64@0.18.17: 211 | resolution: {integrity: sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==} 212 | engines: {node: '>=12'} 213 | cpu: [x64] 214 | os: [sunos] 215 | requiresBuild: true 216 | dev: true 217 | optional: true 218 | 219 | /@esbuild/win32-arm64@0.18.17: 220 | resolution: {integrity: sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==} 221 | engines: {node: '>=12'} 222 | cpu: [arm64] 223 | os: [win32] 224 | requiresBuild: true 225 | dev: true 226 | optional: true 227 | 228 | /@esbuild/win32-ia32@0.18.17: 229 | resolution: {integrity: sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==} 230 | engines: {node: '>=12'} 231 | cpu: [ia32] 232 | os: [win32] 233 | requiresBuild: true 234 | dev: true 235 | optional: true 236 | 237 | /@esbuild/win32-x64@0.18.17: 238 | resolution: {integrity: sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==} 239 | engines: {node: '>=12'} 240 | cpu: [x64] 241 | os: [win32] 242 | requiresBuild: true 243 | dev: true 244 | optional: true 245 | 246 | /@jridgewell/gen-mapping@0.3.3: 247 | resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} 248 | engines: {node: '>=6.0.0'} 249 | dependencies: 250 | '@jridgewell/set-array': 1.1.2 251 | '@jridgewell/sourcemap-codec': 1.4.15 252 | '@jridgewell/trace-mapping': 0.3.18 253 | dev: true 254 | 255 | /@jridgewell/resolve-uri@3.1.0: 256 | resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} 257 | engines: {node: '>=6.0.0'} 258 | dev: true 259 | 260 | /@jridgewell/set-array@1.1.2: 261 | resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} 262 | engines: {node: '>=6.0.0'} 263 | dev: true 264 | 265 | /@jridgewell/sourcemap-codec@1.4.14: 266 | resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} 267 | dev: true 268 | 269 | /@jridgewell/sourcemap-codec@1.4.15: 270 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 271 | dev: true 272 | 273 | /@jridgewell/trace-mapping@0.3.18: 274 | resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} 275 | dependencies: 276 | '@jridgewell/resolve-uri': 3.1.0 277 | '@jridgewell/sourcemap-codec': 1.4.14 278 | dev: true 279 | 280 | /@nodelib/fs.scandir@2.1.5: 281 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 282 | engines: {node: '>= 8'} 283 | dependencies: 284 | '@nodelib/fs.stat': 2.0.5 285 | run-parallel: 1.2.0 286 | dev: true 287 | 288 | /@nodelib/fs.stat@2.0.5: 289 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 290 | engines: {node: '>= 8'} 291 | dev: true 292 | 293 | /@nodelib/fs.walk@1.2.8: 294 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 295 | engines: {node: '>= 8'} 296 | dependencies: 297 | '@nodelib/fs.scandir': 2.1.5 298 | fastq: 1.15.0 299 | dev: true 300 | 301 | /@types/chrome@0.0.243: 302 | resolution: {integrity: sha512-4PHv0kxxxpZFHWPBiJJ9TWH8kbx0567j1b2djnhpJjpiSGNI7UKkz7dSEECBtQ0B3N5nQTMwSB/5IopkWGAbEA==} 303 | dependencies: 304 | '@types/filesystem': 0.0.32 305 | '@types/har-format': 1.2.11 306 | dev: true 307 | 308 | /@types/filesystem@0.0.32: 309 | resolution: {integrity: sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==} 310 | dependencies: 311 | '@types/filewriter': 0.0.29 312 | dev: true 313 | 314 | /@types/filewriter@0.0.29: 315 | resolution: {integrity: sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==} 316 | dev: true 317 | 318 | /@types/har-format@1.2.11: 319 | resolution: {integrity: sha512-T232/TneofqK30AD1LRrrf8KnjLvzrjWDp7eWST5KoiSzrBfRsLrWDPk4STQPW4NZG6v2MltnduBVmakbZOBIQ==} 320 | dev: true 321 | 322 | /@types/lodash-es@4.17.8: 323 | resolution: {integrity: sha512-euY3XQcZmIzSy7YH5+Unb3b2X12Wtk54YWINBvvGQ5SmMvwb11JQskGsfkH/5HXK77Kr8GF0wkVDIxzAisWtog==} 324 | dependencies: 325 | '@types/lodash': 4.14.196 326 | dev: true 327 | 328 | /@types/lodash@4.14.196: 329 | resolution: {integrity: sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==} 330 | dev: true 331 | 332 | /@types/node@20.4.8: 333 | resolution: {integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==} 334 | dev: true 335 | 336 | /any-promise@1.3.0: 337 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 338 | dev: true 339 | 340 | /anymatch@3.1.3: 341 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 342 | engines: {node: '>= 8'} 343 | dependencies: 344 | normalize-path: 3.0.0 345 | picomatch: 2.3.1 346 | dev: true 347 | 348 | /arg@5.0.2: 349 | resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 350 | dev: true 351 | 352 | /array-union@2.1.0: 353 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 354 | engines: {node: '>=8'} 355 | dev: true 356 | 357 | /balanced-match@1.0.2: 358 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 359 | dev: true 360 | 361 | /binary-extensions@2.2.0: 362 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 363 | engines: {node: '>=8'} 364 | dev: true 365 | 366 | /brace-expansion@1.1.11: 367 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 368 | dependencies: 369 | balanced-match: 1.0.2 370 | concat-map: 0.0.1 371 | dev: true 372 | 373 | /braces@3.0.2: 374 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 375 | engines: {node: '>=8'} 376 | dependencies: 377 | fill-range: 7.0.1 378 | dev: true 379 | 380 | /bundle-require@4.0.1(esbuild@0.18.17): 381 | resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} 382 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 383 | peerDependencies: 384 | esbuild: '>=0.17' 385 | dependencies: 386 | esbuild: 0.18.17 387 | load-tsconfig: 0.2.5 388 | dev: true 389 | 390 | /cac@6.7.14: 391 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 392 | engines: {node: '>=8'} 393 | dev: true 394 | 395 | /camelcase-css@2.0.1: 396 | resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 397 | engines: {node: '>= 6'} 398 | dev: true 399 | 400 | /chokidar@3.5.3: 401 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 402 | engines: {node: '>= 8.10.0'} 403 | dependencies: 404 | anymatch: 3.1.3 405 | braces: 3.0.2 406 | glob-parent: 5.1.2 407 | is-binary-path: 2.1.0 408 | is-glob: 4.0.3 409 | normalize-path: 3.0.0 410 | readdirp: 3.6.0 411 | optionalDependencies: 412 | fsevents: 2.3.2 413 | dev: true 414 | 415 | /commander@4.1.1: 416 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 417 | engines: {node: '>= 6'} 418 | dev: true 419 | 420 | /concat-map@0.0.1: 421 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 422 | dev: true 423 | 424 | /cross-spawn@7.0.3: 425 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 426 | engines: {node: '>= 8'} 427 | dependencies: 428 | path-key: 3.1.1 429 | shebang-command: 2.0.0 430 | which: 2.0.2 431 | dev: true 432 | 433 | /cssesc@3.0.0: 434 | resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 435 | engines: {node: '>=4'} 436 | hasBin: true 437 | dev: true 438 | 439 | /debug@4.3.4: 440 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 441 | engines: {node: '>=6.0'} 442 | peerDependencies: 443 | supports-color: '*' 444 | peerDependenciesMeta: 445 | supports-color: 446 | optional: true 447 | dependencies: 448 | ms: 2.1.2 449 | dev: true 450 | 451 | /didyoumean@1.2.2: 452 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 453 | dev: true 454 | 455 | /dir-glob@3.0.1: 456 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 457 | engines: {node: '>=8'} 458 | dependencies: 459 | path-type: 4.0.0 460 | dev: true 461 | 462 | /dlv@1.1.3: 463 | resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 464 | dev: true 465 | 466 | /esbuild@0.18.17: 467 | resolution: {integrity: sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==} 468 | engines: {node: '>=12'} 469 | hasBin: true 470 | requiresBuild: true 471 | optionalDependencies: 472 | '@esbuild/android-arm': 0.18.17 473 | '@esbuild/android-arm64': 0.18.17 474 | '@esbuild/android-x64': 0.18.17 475 | '@esbuild/darwin-arm64': 0.18.17 476 | '@esbuild/darwin-x64': 0.18.17 477 | '@esbuild/freebsd-arm64': 0.18.17 478 | '@esbuild/freebsd-x64': 0.18.17 479 | '@esbuild/linux-arm': 0.18.17 480 | '@esbuild/linux-arm64': 0.18.17 481 | '@esbuild/linux-ia32': 0.18.17 482 | '@esbuild/linux-loong64': 0.18.17 483 | '@esbuild/linux-mips64el': 0.18.17 484 | '@esbuild/linux-ppc64': 0.18.17 485 | '@esbuild/linux-riscv64': 0.18.17 486 | '@esbuild/linux-s390x': 0.18.17 487 | '@esbuild/linux-x64': 0.18.17 488 | '@esbuild/netbsd-x64': 0.18.17 489 | '@esbuild/openbsd-x64': 0.18.17 490 | '@esbuild/sunos-x64': 0.18.17 491 | '@esbuild/win32-arm64': 0.18.17 492 | '@esbuild/win32-ia32': 0.18.17 493 | '@esbuild/win32-x64': 0.18.17 494 | dev: true 495 | 496 | /execa@5.1.1: 497 | resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 498 | engines: {node: '>=10'} 499 | dependencies: 500 | cross-spawn: 7.0.3 501 | get-stream: 6.0.1 502 | human-signals: 2.1.0 503 | is-stream: 2.0.1 504 | merge-stream: 2.0.0 505 | npm-run-path: 4.0.1 506 | onetime: 5.1.2 507 | signal-exit: 3.0.7 508 | strip-final-newline: 2.0.0 509 | dev: true 510 | 511 | /fast-glob@3.3.1: 512 | resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} 513 | engines: {node: '>=8.6.0'} 514 | dependencies: 515 | '@nodelib/fs.stat': 2.0.5 516 | '@nodelib/fs.walk': 1.2.8 517 | glob-parent: 5.1.2 518 | merge2: 1.4.1 519 | micromatch: 4.0.5 520 | dev: true 521 | 522 | /fastq@1.15.0: 523 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 524 | dependencies: 525 | reusify: 1.0.4 526 | dev: true 527 | 528 | /fill-range@7.0.1: 529 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 530 | engines: {node: '>=8'} 531 | dependencies: 532 | to-regex-range: 5.0.1 533 | dev: true 534 | 535 | /fs.realpath@1.0.0: 536 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 537 | dev: true 538 | 539 | /fsevents@2.3.2: 540 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 541 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 542 | os: [darwin] 543 | requiresBuild: true 544 | dev: true 545 | optional: true 546 | 547 | /function-bind@1.1.1: 548 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 549 | dev: true 550 | 551 | /get-stream@6.0.1: 552 | resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 553 | engines: {node: '>=10'} 554 | dev: true 555 | 556 | /glob-parent@5.1.2: 557 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 558 | engines: {node: '>= 6'} 559 | dependencies: 560 | is-glob: 4.0.3 561 | dev: true 562 | 563 | /glob-parent@6.0.2: 564 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 565 | engines: {node: '>=10.13.0'} 566 | dependencies: 567 | is-glob: 4.0.3 568 | dev: true 569 | 570 | /glob@7.1.6: 571 | resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} 572 | dependencies: 573 | fs.realpath: 1.0.0 574 | inflight: 1.0.6 575 | inherits: 2.0.4 576 | minimatch: 3.1.2 577 | once: 1.4.0 578 | path-is-absolute: 1.0.1 579 | dev: true 580 | 581 | /globby@11.1.0: 582 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 583 | engines: {node: '>=10'} 584 | dependencies: 585 | array-union: 2.1.0 586 | dir-glob: 3.0.1 587 | fast-glob: 3.3.1 588 | ignore: 5.2.4 589 | merge2: 1.4.1 590 | slash: 3.0.0 591 | dev: true 592 | 593 | /has@1.0.3: 594 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 595 | engines: {node: '>= 0.4.0'} 596 | dependencies: 597 | function-bind: 1.1.1 598 | dev: true 599 | 600 | /human-signals@2.1.0: 601 | resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 602 | engines: {node: '>=10.17.0'} 603 | dev: true 604 | 605 | /ignore@5.2.4: 606 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 607 | engines: {node: '>= 4'} 608 | dev: true 609 | 610 | /inflight@1.0.6: 611 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 612 | dependencies: 613 | once: 1.4.0 614 | wrappy: 1.0.2 615 | dev: true 616 | 617 | /inherits@2.0.4: 618 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 619 | dev: true 620 | 621 | /is-binary-path@2.1.0: 622 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 623 | engines: {node: '>=8'} 624 | dependencies: 625 | binary-extensions: 2.2.0 626 | dev: true 627 | 628 | /is-core-module@2.13.0: 629 | resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} 630 | dependencies: 631 | has: 1.0.3 632 | dev: true 633 | 634 | /is-extglob@2.1.1: 635 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 636 | engines: {node: '>=0.10.0'} 637 | dev: true 638 | 639 | /is-glob@4.0.3: 640 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 641 | engines: {node: '>=0.10.0'} 642 | dependencies: 643 | is-extglob: 2.1.1 644 | dev: true 645 | 646 | /is-number@7.0.0: 647 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 648 | engines: {node: '>=0.12.0'} 649 | dev: true 650 | 651 | /is-stream@2.0.1: 652 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 653 | engines: {node: '>=8'} 654 | dev: true 655 | 656 | /isexe@2.0.0: 657 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 658 | dev: true 659 | 660 | /jiti@1.19.1: 661 | resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==} 662 | hasBin: true 663 | dev: true 664 | 665 | /joycon@3.1.1: 666 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 667 | engines: {node: '>=10'} 668 | dev: true 669 | 670 | /ky@0.33.3: 671 | resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} 672 | engines: {node: '>=14.16'} 673 | dev: false 674 | 675 | /lilconfig@2.1.0: 676 | resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 677 | engines: {node: '>=10'} 678 | dev: true 679 | 680 | /lines-and-columns@1.2.4: 681 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 682 | dev: true 683 | 684 | /load-tsconfig@0.2.5: 685 | resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 686 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 687 | dev: true 688 | 689 | /lodash-es@4.17.21: 690 | resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} 691 | dev: false 692 | 693 | /lodash.sortby@4.7.0: 694 | resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} 695 | dev: true 696 | 697 | /merge-stream@2.0.0: 698 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 699 | dev: true 700 | 701 | /merge2@1.4.1: 702 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 703 | engines: {node: '>= 8'} 704 | dev: true 705 | 706 | /micromatch@4.0.5: 707 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 708 | engines: {node: '>=8.6'} 709 | dependencies: 710 | braces: 3.0.2 711 | picomatch: 2.3.1 712 | dev: true 713 | 714 | /mimic-fn@2.1.0: 715 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 716 | engines: {node: '>=6'} 717 | dev: true 718 | 719 | /minimatch@3.1.2: 720 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 721 | dependencies: 722 | brace-expansion: 1.1.11 723 | dev: true 724 | 725 | /ms@2.1.2: 726 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 727 | dev: true 728 | 729 | /mz@2.7.0: 730 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 731 | dependencies: 732 | any-promise: 1.3.0 733 | object-assign: 4.1.1 734 | thenify-all: 1.6.0 735 | dev: true 736 | 737 | /nanoid@3.3.6: 738 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 739 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 740 | hasBin: true 741 | dev: true 742 | 743 | /normalize-path@3.0.0: 744 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 745 | engines: {node: '>=0.10.0'} 746 | dev: true 747 | 748 | /npm-run-path@4.0.1: 749 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 750 | engines: {node: '>=8'} 751 | dependencies: 752 | path-key: 3.1.1 753 | dev: true 754 | 755 | /object-assign@4.1.1: 756 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 757 | engines: {node: '>=0.10.0'} 758 | dev: true 759 | 760 | /object-hash@3.0.0: 761 | resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 762 | engines: {node: '>= 6'} 763 | dev: true 764 | 765 | /once@1.4.0: 766 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 767 | dependencies: 768 | wrappy: 1.0.2 769 | dev: true 770 | 771 | /onetime@5.1.2: 772 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 773 | engines: {node: '>=6'} 774 | dependencies: 775 | mimic-fn: 2.1.0 776 | dev: true 777 | 778 | /path-is-absolute@1.0.1: 779 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 780 | engines: {node: '>=0.10.0'} 781 | dev: true 782 | 783 | /path-key@3.1.1: 784 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 785 | engines: {node: '>=8'} 786 | dev: true 787 | 788 | /path-parse@1.0.7: 789 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 790 | dev: true 791 | 792 | /path-type@4.0.0: 793 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 794 | engines: {node: '>=8'} 795 | dev: true 796 | 797 | /picocolors@1.0.0: 798 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 799 | dev: true 800 | 801 | /picomatch@2.3.1: 802 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 803 | engines: {node: '>=8.6'} 804 | dev: true 805 | 806 | /pify@2.3.0: 807 | resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 808 | engines: {node: '>=0.10.0'} 809 | dev: true 810 | 811 | /pirates@4.0.6: 812 | resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} 813 | engines: {node: '>= 6'} 814 | dev: true 815 | 816 | /postcss-import@15.1.0(postcss@8.4.27): 817 | resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 818 | engines: {node: '>=14.0.0'} 819 | peerDependencies: 820 | postcss: ^8.0.0 821 | dependencies: 822 | postcss: 8.4.27 823 | postcss-value-parser: 4.2.0 824 | read-cache: 1.0.0 825 | resolve: 1.22.4 826 | dev: true 827 | 828 | /postcss-js@4.0.1(postcss@8.4.27): 829 | resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 830 | engines: {node: ^12 || ^14 || >= 16} 831 | peerDependencies: 832 | postcss: ^8.4.21 833 | dependencies: 834 | camelcase-css: 2.0.1 835 | postcss: 8.4.27 836 | dev: true 837 | 838 | /postcss-load-config@4.0.1(postcss@8.4.27): 839 | resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} 840 | engines: {node: '>= 14'} 841 | peerDependencies: 842 | postcss: '>=8.0.9' 843 | ts-node: '>=9.0.0' 844 | peerDependenciesMeta: 845 | postcss: 846 | optional: true 847 | ts-node: 848 | optional: true 849 | dependencies: 850 | lilconfig: 2.1.0 851 | postcss: 8.4.27 852 | yaml: 2.3.1 853 | dev: true 854 | 855 | /postcss-nested@6.0.1(postcss@8.4.27): 856 | resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} 857 | engines: {node: '>=12.0'} 858 | peerDependencies: 859 | postcss: ^8.2.14 860 | dependencies: 861 | postcss: 8.4.27 862 | postcss-selector-parser: 6.0.13 863 | dev: true 864 | 865 | /postcss-selector-parser@6.0.13: 866 | resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} 867 | engines: {node: '>=4'} 868 | dependencies: 869 | cssesc: 3.0.0 870 | util-deprecate: 1.0.2 871 | dev: true 872 | 873 | /postcss-value-parser@4.2.0: 874 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 875 | dev: true 876 | 877 | /postcss@8.4.27: 878 | resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==} 879 | engines: {node: ^10 || ^12 || >=14} 880 | dependencies: 881 | nanoid: 3.3.6 882 | picocolors: 1.0.0 883 | source-map-js: 1.0.2 884 | dev: true 885 | 886 | /punycode@2.3.0: 887 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 888 | engines: {node: '>=6'} 889 | dev: true 890 | 891 | /queue-microtask@1.2.3: 892 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 893 | dev: true 894 | 895 | /read-cache@1.0.0: 896 | resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 897 | dependencies: 898 | pify: 2.3.0 899 | dev: true 900 | 901 | /readdirp@3.6.0: 902 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 903 | engines: {node: '>=8.10.0'} 904 | dependencies: 905 | picomatch: 2.3.1 906 | dev: true 907 | 908 | /resolve-from@5.0.0: 909 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 910 | engines: {node: '>=8'} 911 | dev: true 912 | 913 | /resolve@1.22.4: 914 | resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} 915 | hasBin: true 916 | dependencies: 917 | is-core-module: 2.13.0 918 | path-parse: 1.0.7 919 | supports-preserve-symlinks-flag: 1.0.0 920 | dev: true 921 | 922 | /reusify@1.0.4: 923 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 924 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 925 | dev: true 926 | 927 | /rollup@3.27.2: 928 | resolution: {integrity: sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==} 929 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 930 | hasBin: true 931 | optionalDependencies: 932 | fsevents: 2.3.2 933 | dev: true 934 | 935 | /run-parallel@1.2.0: 936 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 937 | dependencies: 938 | queue-microtask: 1.2.3 939 | dev: true 940 | 941 | /shebang-command@2.0.0: 942 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 943 | engines: {node: '>=8'} 944 | dependencies: 945 | shebang-regex: 3.0.0 946 | dev: true 947 | 948 | /shebang-regex@3.0.0: 949 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 950 | engines: {node: '>=8'} 951 | dev: true 952 | 953 | /signal-exit@3.0.7: 954 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 955 | dev: true 956 | 957 | /slash@3.0.0: 958 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 959 | engines: {node: '>=8'} 960 | dev: true 961 | 962 | /source-map-js@1.0.2: 963 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 964 | engines: {node: '>=0.10.0'} 965 | dev: true 966 | 967 | /source-map@0.8.0-beta.0: 968 | resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} 969 | engines: {node: '>= 8'} 970 | dependencies: 971 | whatwg-url: 7.1.0 972 | dev: true 973 | 974 | /strip-final-newline@2.0.0: 975 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 976 | engines: {node: '>=6'} 977 | dev: true 978 | 979 | /sucrase@3.34.0: 980 | resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} 981 | engines: {node: '>=8'} 982 | hasBin: true 983 | dependencies: 984 | '@jridgewell/gen-mapping': 0.3.3 985 | commander: 4.1.1 986 | glob: 7.1.6 987 | lines-and-columns: 1.2.4 988 | mz: 2.7.0 989 | pirates: 4.0.6 990 | ts-interface-checker: 0.1.13 991 | dev: true 992 | 993 | /supports-preserve-symlinks-flag@1.0.0: 994 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 995 | engines: {node: '>= 0.4'} 996 | dev: true 997 | 998 | /tailwindcss@3.3.3: 999 | resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==} 1000 | engines: {node: '>=14.0.0'} 1001 | hasBin: true 1002 | dependencies: 1003 | '@alloc/quick-lru': 5.2.0 1004 | arg: 5.0.2 1005 | chokidar: 3.5.3 1006 | didyoumean: 1.2.2 1007 | dlv: 1.1.3 1008 | fast-glob: 3.3.1 1009 | glob-parent: 6.0.2 1010 | is-glob: 4.0.3 1011 | jiti: 1.19.1 1012 | lilconfig: 2.1.0 1013 | micromatch: 4.0.5 1014 | normalize-path: 3.0.0 1015 | object-hash: 3.0.0 1016 | picocolors: 1.0.0 1017 | postcss: 8.4.27 1018 | postcss-import: 15.1.0(postcss@8.4.27) 1019 | postcss-js: 4.0.1(postcss@8.4.27) 1020 | postcss-load-config: 4.0.1(postcss@8.4.27) 1021 | postcss-nested: 6.0.1(postcss@8.4.27) 1022 | postcss-selector-parser: 6.0.13 1023 | resolve: 1.22.4 1024 | sucrase: 3.34.0 1025 | transitivePeerDependencies: 1026 | - ts-node 1027 | dev: true 1028 | 1029 | /thenify-all@1.6.0: 1030 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 1031 | engines: {node: '>=0.8'} 1032 | dependencies: 1033 | thenify: 3.3.1 1034 | dev: true 1035 | 1036 | /thenify@3.3.1: 1037 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 1038 | dependencies: 1039 | any-promise: 1.3.0 1040 | dev: true 1041 | 1042 | /to-regex-range@5.0.1: 1043 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1044 | engines: {node: '>=8.0'} 1045 | dependencies: 1046 | is-number: 7.0.0 1047 | dev: true 1048 | 1049 | /tr46@1.0.1: 1050 | resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} 1051 | dependencies: 1052 | punycode: 2.3.0 1053 | dev: true 1054 | 1055 | /tree-kill@1.2.2: 1056 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 1057 | hasBin: true 1058 | dev: true 1059 | 1060 | /ts-interface-checker@0.1.13: 1061 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 1062 | dev: true 1063 | 1064 | /tsup@7.2.0(postcss@8.4.27)(typescript@5.1.3): 1065 | resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==} 1066 | engines: {node: '>=16.14'} 1067 | hasBin: true 1068 | peerDependencies: 1069 | '@swc/core': ^1 1070 | postcss: ^8.4.12 1071 | typescript: '>=4.1.0' 1072 | peerDependenciesMeta: 1073 | '@swc/core': 1074 | optional: true 1075 | postcss: 1076 | optional: true 1077 | typescript: 1078 | optional: true 1079 | dependencies: 1080 | bundle-require: 4.0.1(esbuild@0.18.17) 1081 | cac: 6.7.14 1082 | chokidar: 3.5.3 1083 | debug: 4.3.4 1084 | esbuild: 0.18.17 1085 | execa: 5.1.1 1086 | globby: 11.1.0 1087 | joycon: 3.1.1 1088 | postcss: 8.4.27 1089 | postcss-load-config: 4.0.1(postcss@8.4.27) 1090 | resolve-from: 5.0.0 1091 | rollup: 3.27.2 1092 | source-map: 0.8.0-beta.0 1093 | sucrase: 3.34.0 1094 | tree-kill: 1.2.2 1095 | typescript: 5.1.3 1096 | transitivePeerDependencies: 1097 | - supports-color 1098 | - ts-node 1099 | dev: true 1100 | 1101 | /typescript@5.1.3: 1102 | resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} 1103 | engines: {node: '>=14.17'} 1104 | hasBin: true 1105 | dev: true 1106 | 1107 | /util-deprecate@1.0.2: 1108 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1109 | dev: true 1110 | 1111 | /webidl-conversions@4.0.2: 1112 | resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} 1113 | dev: true 1114 | 1115 | /whatwg-url@7.1.0: 1116 | resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} 1117 | dependencies: 1118 | lodash.sortby: 4.7.0 1119 | tr46: 1.0.1 1120 | webidl-conversions: 4.0.2 1121 | dev: true 1122 | 1123 | /which@2.0.2: 1124 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1125 | engines: {node: '>= 8'} 1126 | hasBin: true 1127 | dependencies: 1128 | isexe: 2.0.0 1129 | dev: true 1130 | 1131 | /wrappy@1.0.2: 1132 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1133 | dev: true 1134 | 1135 | /yaml@2.3.1: 1136 | resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} 1137 | engines: {node: '>= 14'} 1138 | dev: true 1139 | 1140 | /zod-to-json-schema@3.21.4(zod@3.21.4): 1141 | resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==} 1142 | peerDependencies: 1143 | zod: ^3.21.4 1144 | dependencies: 1145 | zod: 3.21.4 1146 | dev: false 1147 | 1148 | /zod@3.21.4: 1149 | resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} 1150 | dev: false 1151 | --------------------------------------------------------------------------------