diff --git a/extensions/finicky-rule-manager/.gitignore b/extensions/finicky-rule-manager/.gitignore new file mode 100644 index 00000000000..84fc7188f51 --- /dev/null +++ b/extensions/finicky-rule-manager/.gitignore @@ -0,0 +1,14 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# Raycast specific files +raycast-env.d.ts +.raycast-swift-build +.swiftpm +compiled_raycast_swift +compiled_raycast_rust + +# misc +.DS_Store diff --git a/extensions/finicky-rule-manager/.prettierrc b/extensions/finicky-rule-manager/.prettierrc new file mode 100644 index 00000000000..fc0f5030683 --- /dev/null +++ b/extensions/finicky-rule-manager/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 120, + "singleQuote": false +} diff --git a/extensions/finicky-rule-manager/CHANGELOG.md b/extensions/finicky-rule-manager/CHANGELOG.md new file mode 100644 index 00000000000..a5ffd8106f7 --- /dev/null +++ b/extensions/finicky-rule-manager/CHANGELOG.md @@ -0,0 +1,3 @@ +# Finicky Rule Manager Changelog + +## [Initial Version] - 2026-05-05 diff --git a/extensions/finicky-rule-manager/README.md b/extensions/finicky-rule-manager/README.md new file mode 100644 index 00000000000..9f122851340 --- /dev/null +++ b/extensions/finicky-rule-manager/README.md @@ -0,0 +1,174 @@ +# Finicky Rule Manager + +Manage [Finicky](https://github.com/johnste/finicky) browser routing rules directly from Raycast. + +## Features + +- **AI-Powered Rule Management**: Use natural language in Raycast AI Chat to create, update, and manage rules +- **Visual Rule Management**: Create, edit, enable/disable, and delete Finicky rules through an intuitive UI +- **Create from Browser Tabs**: Instantly create rules from your currently open browser tabs +- **Change Default Browser**: Quick visual browser selector with app icons +- **Guided Rule Builder**: Step-by-step form to build URL patterns without knowing the syntax +- **Browser Auto-Detection**: Automatically detects installed browsers for easy selection +- **Conflict Detection**: Automatically find and resolve conflicting rules +- **Modern ES Module Syntax**: Generates config files using `export default` instead of `module.exports` +- **Automatic Config Generation**: Rules are stored in Raycast Local Storage and automatically generate your `.finicky.js` config file +- **Two Match Types**: + - **Wildcards**: Use Finicky's native wildcard patterns (e.g., `*://*.salesforce.com/*`) + - **Regex**: Test patterns against the full URL string (case-insensitive by default) +- **Quick Actions**: Toggle rules on/off, open config file, reload rules +- **Safe Deletion**: Confirmation dialog before deleting rules + +## Setup + +1. Install the extension +2. Open extension preferences (⌘,) and configure: + - **Finicky Config Path**: Path to your Finicky config file (e.g., `~/.finicky.js`) + - **Default Browser**: Your default browser (e.g., `Brave Browser`, `Arc`, `Safari`) + +## Usage + +### Using AI Chat (Easiest Method) + +The extension integrates with Raycast AI Chat, allowing you to manage rules using natural language: + +1. Open Raycast AI Chat (⌘ Space, then type "AI Chat") +2. Mention the extension with `@Finicky Rule Manager` +3. Ask questions or give commands in plain English: + +**Example commands:** + +- "Send all google.com subpages to Chrome" +- "Create a rule for Salesforce that opens in Arc" +- "Show me all my rules" +- "Find conflicts in my rules" +- "Delete the Salesforce rule" +- "Disable the Google rule" +- "What rules do I have for GitHub?" + +The AI will: + +- ✓ Automatically detect conflicts before creating rules +- ✓ Ask clarifying questions if your request is ambiguous +- ✓ Confirm destructive actions before executing +- ✓ Provide helpful suggestions for pattern matching + +**Note**: Requires Raycast Pro for AI features. + +### Creating a Rule from a Browser Tab + +The fastest way to create a rule is from an open browser tab: + +1. Run the "Create Rule from Browser Tab" command +2. Browse or search through your currently open tabs (grouped by domain) +3. Select a tab to create a rule for it +4. The rule is automatically created with: + - **Name**: Tab title or domain + - **Pattern**: Auto-generated to match the domain and all subdomains (e.g., `*://*.google.com/*`) + - **Browser**: Your configured default browser + +**Note**: Requires the [Raycast Browser Extension](https://www.raycast.com/browser-extension) to be installed. + +### Changing the Default Browser + +Quickly change your Finicky default browser with a visual browser selector: + +1. Run the "Change Default Browser" command +2. Browse the list of detected browsers (with app icons) +3. Click on any browser to set it as your new default +4. Your `.finicky.js` config file is automatically updated + +**Features:** + +- Shows all installed browsers with their actual app icons +- Current default browser is marked with a ✓ checkmark +- Live updates - the checkmark moves immediately after selection +- Searchable list for quick filtering +- Real-time toast notifications showing progress and success + +### Creating a Rule Manually + +#### Guided Mode (Recommended for Beginners) + +1. Run the "Manage Finicky Rules" command +2. Press ⌘N or select "Create New Rule" +3. Use the guided form to build your pattern: + - **Protocol**: Choose http, https, or any + - **Subdomain**: Choose any, none, www, or enter a custom subdomain + - **Domain**: Enter the domain (e.g., `google.com`) + - **Path**: Choose any path, no path, or enter a custom path + - **Browser**: Select from detected browsers or enter manually +4. See a live preview of your pattern as you build it + +#### Manual Mode (For Advanced Users) + +1. Press ⌘T to switch to manual mode +2. Enter patterns directly (one per line): + - **Name**: Display name for the rule + - **Enabled**: Whether the rule is active + - **Match Type**: Choose between wildcards or regex + - **Patterns**: One pattern per line + - **Browser**: Select from detected browsers or enter manually (⌘B to toggle) + +### Example Rules + +#### Salesforce (Regex) + +- Name: `Salesforce` +- Match Type: `regex` +- Patterns: `salesforce` +- Browser: `Arc` + +#### Google & Gmail (Wildcards) + +- Name: `Google Services` +- Match Type: `wildcards` +- Patterns: + + ```text + *://*.google.com/* + *://google.com/* + *://mail.google.com/* + ``` + +- Browser: `Arc` + +## Importing Existing Rules + +### Auto-Import on First Launch + +When you first run the extension, if you have an existing `.finicky.js` file but no rules in Raycast storage, you'll be prompted to import your existing rules. This ensures you don't lose any work you've already done. + +### Manual Import + +You can manually import rules at any time: + +1. Press ⌘I or select "Import from Config File" +2. The extension will parse your `.finicky.js` and extract rules +3. If you already have rules, you'll be asked to **Merge** (keep both) or **Replace** (delete existing) + +**Supported patterns:** + +- Wildcard arrays: `match: ["*://*.example.com/*"]` +- Regex functions: `match: ({ urlString }) => /pattern/i.test(urlString)` +- RegExp constructors: `match: ({ urlString }) => new RegExp("pattern", "i").test(urlString)` + +## How It Works + +This extension treats your Finicky config as **generated**. Rules are stored in Raycast Local Storage as structured JSON, and the `.finicky.js` file is rewritten whenever rules change. + +⚠️ **Important**: After importing, do not manually edit your `.finicky.js` file, as changes will be overwritten. Use the extension to manage all rules. + +## Commands + +This extension provides the following commands: + +1. **Manage Finicky Rules** - Main command for viewing and managing all rules +2. **Create Rule from Browser Tab** - Create rules from currently open browser tabs +3. **Change Default Browser** - Visual browser selector to change your default browser +4. **AI Tools** (via Raycast AI Chat): + - `list-rules` - List all rules with details + - `create-rule` - Create a new rule with conflict detection + - `update-rule` - Update an existing rule + - `delete-rule` - Delete a rule by ID or name + - `find-conflicts` - Find and list conflicting rules diff --git a/extensions/finicky-rule-manager/assets/extension-icon.png b/extensions/finicky-rule-manager/assets/extension-icon.png new file mode 100644 index 00000000000..8651856c34d Binary files /dev/null and b/extensions/finicky-rule-manager/assets/extension-icon.png differ diff --git a/extensions/finicky-rule-manager/eslint.config.mjs b/extensions/finicky-rule-manager/eslint.config.mjs new file mode 100644 index 00000000000..a17709640a9 --- /dev/null +++ b/extensions/finicky-rule-manager/eslint.config.mjs @@ -0,0 +1,4 @@ +import { defineConfig } from "eslint/config"; +import raycastConfig from "@raycast/eslint-config"; + +export default defineConfig([...raycastConfig]); diff --git a/extensions/finicky-rule-manager/metadata/finicky-rule-manager-1.png b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-1.png new file mode 100644 index 00000000000..e91469342a6 Binary files /dev/null and b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-1.png differ diff --git a/extensions/finicky-rule-manager/metadata/finicky-rule-manager-2.png b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-2.png new file mode 100644 index 00000000000..ab8a361bb3a Binary files /dev/null and b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-2.png differ diff --git a/extensions/finicky-rule-manager/metadata/finicky-rule-manager-3.png b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-3.png new file mode 100644 index 00000000000..c9572123306 Binary files /dev/null and b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-3.png differ diff --git a/extensions/finicky-rule-manager/metadata/finicky-rule-manager-4.png b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-4.png new file mode 100644 index 00000000000..b7be21da4fb Binary files /dev/null and b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-4.png differ diff --git a/extensions/finicky-rule-manager/metadata/finicky-rule-manager-5.png b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-5.png new file mode 100644 index 00000000000..2f3180741c5 Binary files /dev/null and b/extensions/finicky-rule-manager/metadata/finicky-rule-manager-5.png differ diff --git a/extensions/finicky-rule-manager/package-lock.json b/extensions/finicky-rule-manager/package-lock.json new file mode 100644 index 00000000000..ef5726e3201 --- /dev/null +++ b/extensions/finicky-rule-manager/package-lock.json @@ -0,0 +1,2764 @@ +{ + "name": "finicky-rule-manager", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "finicky-rule-manager", + "license": "MIT", + "dependencies": { + "@raycast/api": "^1.104.15", + "@raycast/utils": "^2.2.4" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.1.1", + "@types/node": "^25.6.0", + "@types/react": "19.2.14", + "eslint": "^10.3.0", + "prettier": "^3.8.3", + "typescript": "^6.0.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@oclif/core": { + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.5.tgz", + "integrity": "sha512-qcdCF7NrdWPfme6Kr34wwljRCXbCVpL1WVxiNy0Ep6vbWKjxAjFQwuhqkoyL0yjI+KdwtLcOCGn5z2yzdijc8w==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.45.tgz", + "integrity": "sha512-ENrUg8rbVCjh40uvi3MC9kGbiUoEf11nyqE59RBzegeeLpRXNo/Zp27L9j1tUmPEqGgfS2/wvHPihNzkpK1FDw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4", + "ansis": "^3.16.0", + "debug": "^4.4.1", + "ejs": "^3.1.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-help": { + "version": "6.2.44", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.44.tgz", + "integrity": "sha512-x03Se2LtlOOlGfTuuubt5C4Z8NHeR4zKXtVnfycuLU+2VOMu2WpsGy9nbs3nYuInuvsIY1BizjVaTjUz060Sig==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-not-found": { + "version": "3.2.80", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.80.tgz", + "integrity": "sha512-yTLjWvR1r/Rd/cO2LxHdMCDoL5sQhBYRUcOMCmxZtWVWhx4rAZ8KVUPDVsb+SvjJDV5ADTDBgt1H52fFx7YWqg==", + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.10.1", + "@oclif/core": "^4.10.5", + "ansis": "^3.17.0", + "fast-levenshtein": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@raycast/api": { + "version": "1.104.15", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.104.15.tgz", + "integrity": "sha512-jQK0J1E8MRrjIDoG1QP/Py+eMdDRfxS2wRHd8z40Fa8bBxUQyDr+A4zJ+7s61V8vPCzqiGgET6pvH61AYmR25g==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4.8.4", + "@oclif/plugin-autocomplete": "^3.2.40", + "@oclif/plugin-help": "^6.2.37", + "@oclif/plugin-not-found": "^3.2.74", + "@types/node": "22.19.17", + "@types/react": "19.0.10", + "esbuild": "^0.27.3", + "react": "19.0.0" + }, + "bin": { + "ray": "bin/run.js" + }, + "engines": { + "node": ">=22.22.2" + }, + "peerDependencies": { + "@types/node": "22.19.17", + "@types/react": "19.0.10", + "react-devtools": "6.1.1" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react-devtools": { + "optional": true + } + } + }, + "node_modules/@raycast/api/node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@raycast/api/node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@raycast/api/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/@raycast/eslint-config": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@raycast/eslint-config/-/eslint-config-2.1.1.tgz", + "integrity": "sha512-W0kxF+FJ+BYQn0EKIV739j2ZrHEtjo/LclsoZgUWg3t364Dq75XKcjqYFYx+59/DBaamY0amdajlfuDAf6veAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/js": "^9.36.0", + "@raycast/eslint-plugin": "^2.1.1", + "eslint-config-prettier": "^10.1.8", + "globals": "^16.4.0", + "typescript-eslint": "^8.45.0" + }, + "peerDependencies": { + "eslint": ">=8.23.0", + "prettier": ">=2", + "typescript": ">=4" + } + }, + "node_modules/@raycast/eslint-plugin": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@raycast/eslint-plugin/-/eslint-plugin-2.1.1.tgz", + "integrity": "sha512-r2gs8uIlNp6I2mLOyN/kReGlvigzEeuyQPl4yw7nwLy8Zxjfjhg8txMViaBux8juBWBxbSWq/IfW6ZA50oeOHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.26.1" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/@raycast/utils": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.2.4.tgz", + "integrity": "sha512-XkxW5bUZACxl2zP4M6Qtkj9a1nbOKcRBm96BLkJ6O4Q7L/vFSnc+3pPJ/i0ZLVOExx5LJMJY0jnNPo91xqJnFQ==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "@raycast/api": ">=1.99.4", + "react": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", + "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/type-utils": "8.58.1", + "@typescript-eslint/utils": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", + "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", + "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.1", + "@typescript-eslint/types": "^8.58.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", + "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", + "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", + "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/utils": "8.58.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", + "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", + "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.1", + "@typescript-eslint/tsconfig-utils": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", + "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", + "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", + "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", + "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.1", + "@typescript-eslint/parser": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/utils": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/extensions/finicky-rule-manager/package.json b/extensions/finicky-rule-manager/package.json new file mode 100644 index 00000000000..9318431a5c1 --- /dev/null +++ b/extensions/finicky-rule-manager/package.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "finicky-rule-manager", + "title": "Finicky Rule Manager", + "description": "Manage Finicky browser routing rules from Raycast", + "icon": "extension-icon.png", + "author": "NormC", + "platforms": [ + "macOS" + ], + "categories": [ + "Applications", + "Productivity" + ], + "license": "MIT", + "preferences": [ + { + "name": "configPath", + "type": "textfield", + "required": false, + "title": "Finicky Config Path", + "description": "Path to your Finicky config file (e.g. ~/.finicky.js)", + "placeholder": "~/.finicky.js" + }, + { + "name": "defaultBrowser", + "type": "textfield", + "required": false, + "title": "Default Browser", + "description": "Finicky defaultBrowser (e.g. Brave Browser)", + "default": "Brave Browser", + "placeholder": "Brave Browser" + } + ], + "ai": { + "instructions": "You are a helpful assistant for managing Finicky browser routing rules. You can help users create, update, delete, and find conflicts in their rules using natural language. When creating rules, always ask clarifying questions if the user's intent is unclear. For URL patterns, use wildcards format like '*://*.example.com/*' for matching domains and subdomains. Common browsers include: Arc, Brave Browser, Google Chrome, Safari, Firefox, Microsoft Edge. Always confirm actions before making destructive changes like deleting rules." + }, + "tools": [ + { + "name": "list-rules", + "title": "List Rules", + "description": "List all Finicky rules with their details", + "mode": "no-view" + }, + { + "name": "create-rule", + "title": "Create Rule", + "description": "Create a new Finicky rule with patterns and browser", + "mode": "no-view" + }, + { + "name": "update-rule", + "title": "Update Rule", + "description": "Update an existing Finicky rule", + "mode": "no-view" + }, + { + "name": "delete-rule", + "title": "Delete Rule", + "description": "Delete a Finicky rule by ID or name", + "mode": "no-view" + }, + { + "name": "find-conflicts", + "title": "Find Conflicts", + "description": "Find and list conflicting rules", + "mode": "no-view" + } + ], + "commands": [ + { + "name": "manage-finicky-rules", + "title": "Manage Finicky Rules", + "subtitle": "Finicky", + "description": "List, create, edit, and delete Finicky rules", + "mode": "view" + }, + { + "name": "create-rule-from-tab", + "title": "Create Rule from Browser Tab", + "subtitle": "Finicky", + "description": "Create a Finicky rule from an open browser tab", + "mode": "view" + }, + { + "name": "change-default-browser", + "title": "Change Default Browser", + "subtitle": "Finicky", + "description": "Change the default browser for Finicky", + "mode": "view" + } + ], + "dependencies": { + "@raycast/api": "^1.104.15", + "@raycast/utils": "^2.2.4" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.1.1", + "@types/node": "^25.6.0", + "@types/react": "19.2.14", + "eslint": "^10.3.0", + "prettier": "^3.8.3", + "typescript": "^6.0.3" + }, + "scripts": { + "build": "ray build", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1", + "publish": "npx @raycast/api@latest publish" + } +} diff --git a/extensions/finicky-rule-manager/src/change-default-browser.tsx b/extensions/finicky-rule-manager/src/change-default-browser.tsx new file mode 100644 index 00000000000..9b1f7f4da47 --- /dev/null +++ b/extensions/finicky-rule-manager/src/change-default-browser.tsx @@ -0,0 +1,109 @@ +import { Action, ActionPanel, List, Icon, showToast, Toast, getPreferenceValues } from "@raycast/api"; +import { getFavicon } from "@raycast/utils"; +import { useEffect, useState } from "react"; +import { getInstalledBrowsers, Browser } from "./utils/browsers"; +import { loadRules, getDefaultBrowser, setDefaultBrowser } from "./storage"; +import { generateFinickyConfig, writeConfigFile } from "./utils/finicky"; +import { expandTilde } from "./utils/path"; + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string }) { + const rules = await loadRules(); + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +export default function Command() { + const [installedBrowsers, setInstalledBrowsers] = useState([]); + const [currentDefaultBrowser, setCurrentDefaultBrowser] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + + useEffect(() => { + async function loadData() { + try { + const [browsers, defaultBrowser] = await Promise.all([getInstalledBrowsers(), getDefaultBrowser()]); + setInstalledBrowsers(browsers); + setCurrentDefaultBrowser(defaultBrowser); + } catch (error) { + console.error("Failed to load data:", error); + } finally { + setIsLoading(false); + } + } + loadData(); + }, []); + + async function handleSetDefault(browser: Browser) { + const toast = await showToast({ + style: Toast.Style.Animated, + title: "Updating default browser...", + message: `Setting to ${browser.name}`, + }); + + try { + await setDefaultBrowser(browser.name); + setCurrentDefaultBrowser(browser.name); + + if (configPath) { + toast.message = "Updating config file..."; + await syncConfigFile({ configPath, defaultBrowser: browser.name }); + + toast.style = Toast.Style.Success; + toast.title = "✓ Default browser updated"; + toast.message = `${browser.name} is now your default browser`; + } else { + toast.style = Toast.Style.Success; + toast.title = "✓ Default browser updated"; + toast.message = `${browser.name} is now your default (config path not set)`; + } + } catch (error) { + console.error("Error updating default browser:", error); + toast.style = Toast.Style.Failure; + toast.title = "✗ Failed to update default browser"; + toast.message = error instanceof Error ? error.message : String(error); + } + } + + return ( + + {installedBrowsers.map((browser) => { + const isDefault = browser.name === currentDefaultBrowser; + return ( + + { + await handleSetDefault(browser); + }} + /> + + } + /> + ); + })} + + {installedBrowsers.length === 0 && !isLoading && ( + + )} + + ); +} diff --git a/extensions/finicky-rule-manager/src/create-rule-from-tab.tsx b/extensions/finicky-rule-manager/src/create-rule-from-tab.tsx new file mode 100644 index 00000000000..f78c2bc6559 --- /dev/null +++ b/extensions/finicky-rule-manager/src/create-rule-from-tab.tsx @@ -0,0 +1,195 @@ +import { Action, ActionPanel, BrowserExtension, Icon, List, showToast, Toast, getPreferenceValues } from "@raycast/api"; +import { useEffect, useState } from "react"; +import { loadRules, saveRules, getDefaultBrowser } from "./storage"; +import { Rule } from "./types"; +import { expandTilde } from "./utils/path"; +import { generateFinickyConfig, writeConfigFile } from "./utils/finicky"; + +interface Tab { + id: number; + title?: string; + url: string; + favicon?: string; + active: boolean; +} + +function uuid(): string { + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +function extractDomain(url: string): string { + try { + const urlObj = new URL(url); + return urlObj.hostname; + } catch { + return ""; + } +} + +function generateRuleName(url: string, title?: string): string { + const domain = extractDomain(url); + if (title && title !== domain) { + return title.length > 50 ? `${title.substring(0, 47)}...` : title; + } + return domain || "New Rule"; +} + +function generatePattern(url: string): string { + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname; + + // Generate a pattern that matches the domain and all subdomains + const domainParts = hostname.split("."); + let baseDomain = hostname; + + // If it's a subdomain (more than 2 parts), extract the base domain + if (domainParts.length > 2) { + baseDomain = domainParts.slice(-2).join("."); + } + + return `*://*.${baseDomain}/*`; + } catch { + return ""; + } +} + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string; rules: Rule[] }) { + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules: args.rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +export default function Command() { + const [tabs, setTabs] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [defaultBrowser, setDefaultBrowser] = useState(""); + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + + useEffect(() => { + async function fetchTabs() { + try { + const [openTabs, browser] = await Promise.all([BrowserExtension.getTabs(), getDefaultBrowser()]); + setTabs(openTabs); + setDefaultBrowser(browser); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to fetch browser tabs", + message: String(error), + }); + } finally { + setIsLoading(false); + } + } + + fetchTabs(); + }, []); + + async function createRuleFromTab(tab: Tab) { + const pattern = generatePattern(tab.url); + if (!pattern) { + await showToast({ + style: Toast.Style.Failure, + title: "Invalid URL", + message: "Could not generate pattern from this tab", + }); + return; + } + + const rule: Rule = { + id: uuid(), + name: generateRuleName(tab.url, tab.title), + enabled: true, + matchType: "wildcards", + patterns: [pattern], + browser: defaultBrowser, + }; + + try { + const existingRules = await loadRules(); + const updatedRules = [...existingRules, rule]; + await saveRules(updatedRules); + + if (configPath) { + await syncConfigFile({ configPath, defaultBrowser, rules: updatedRules }); + } + + await showToast({ + style: Toast.Style.Success, + title: "Rule created", + message: `Pattern: ${pattern}`, + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to create rule", + message: String(error), + }); + } + } + + const groupedTabs = tabs.reduce( + (acc, tab) => { + const domain = extractDomain(tab.url); + if (!acc[domain]) { + acc[domain] = []; + } + acc[domain].push(tab); + return acc; + }, + {} as Record, + ); + + return ( + + {Object.entries(groupedTabs).map(([domain, domainTabs]) => ( + + {domainTabs.map((tab) => ( + + { + await createRuleFromTab(tab); + }} + /> + + + + + } + /> + ))} + + ))} + + {tabs.length === 0 && !isLoading && ( + + )} + + ); +} diff --git a/extensions/finicky-rule-manager/src/manage-finicky-rules.tsx b/extensions/finicky-rule-manager/src/manage-finicky-rules.tsx new file mode 100644 index 00000000000..f59c5e5c7b7 --- /dev/null +++ b/extensions/finicky-rule-manager/src/manage-finicky-rules.tsx @@ -0,0 +1,645 @@ +import { + Action, + ActionPanel, + Alert, + confirmAlert, + Form, + Icon, + LocalStorage, + List, + open, + popToRoot, + showToast, + Toast, + useNavigation, + getPreferenceValues, +} from "@raycast/api"; +import { useEffect, useMemo, useState } from "react"; +import { loadRules, saveRules, getDefaultBrowser, setDefaultBrowser } from "./storage"; +import { Rule } from "./types"; +import { expandTilde } from "./utils/path"; +import { generateFinickyConfig, writeConfigFile } from "./utils/finicky"; +import { parseFinickyConfig } from "./utils/parser"; +import { getInstalledBrowsers, Browser } from "./utils/browsers"; +import fs from "fs/promises"; + +function uuid(): string { + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +const IMPORT_DISMISSED_KEY = "importDismissed"; + +async function isImportDismissed(): Promise { + return (await LocalStorage.getItem(IMPORT_DISMISSED_KEY)) === "true"; +} + +async function setImportDismissed(dismissed: boolean): Promise { + if (dismissed) { + await LocalStorage.setItem(IMPORT_DISMISSED_KEY, "true"); + return; + } + await LocalStorage.removeItem(IMPORT_DISMISSED_KEY); +} + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string; rules: Rule[] }) { + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules: args.rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +function parseExistingPattern(pattern: string) { + // Parse a pattern like "*://*.google.com/*" into its components + const protocolMatch = pattern.match(/^(https?|\*):/); + const protocol = protocolMatch ? (protocolMatch[1] === "*" ? "any" : protocolMatch[1]) : "https"; + + // Remove protocol part + const withoutProtocol = pattern.replace(/^[^:]+:\/\//, ""); + + // Split into host and path + const firstSlash = withoutProtocol.indexOf("/"); + const host = firstSlash === -1 ? withoutProtocol : withoutProtocol.substring(0, firstSlash); + const pathPart = firstSlash === -1 ? "" : withoutProtocol.substring(firstSlash); + + // Parse subdomain and domain + let subdomain = "any"; + let customSubdomain = ""; + let domain = host; + + if (host.startsWith("*.")) { + subdomain = "any"; + domain = host.substring(2); + } else if (host.startsWith("www.")) { + subdomain = "www"; + domain = host.substring(4); + } else if (host.includes(".")) { + const parts = host.split("."); + if (parts.length > 2) { + subdomain = "custom"; + customSubdomain = parts.slice(0, -2).join("."); + domain = parts.slice(-2).join("."); + } else { + subdomain = "none"; + domain = host; + } + } else { + subdomain = "none"; + } + + // Parse path + let path = "any"; + let customPath = ""; + + if (pathPart === "" || pathPart === "/") { + path = "none"; + } else if (pathPart === "/*") { + path = "any"; + } else { + path = "custom"; + customPath = pathPart; + } + + return { protocol, subdomain, customSubdomain, domain, path, customPath }; +} + +function RuleForm(props: { title: string; initial?: Rule; onSubmit: (rule: Rule) => Promise }) { + const { pop } = useNavigation(); + const initial = props.initial; + + // Parse initial pattern if editing and only one pattern exists + const parsedPattern = initial?.patterns?.[0] ? parseExistingPattern(initial.patterns[0]) : null; + const shouldUseManualMode = initial && initial.patterns.length > 1; + + const [inputMode, setInputMode] = useState<"guided" | "manual">(shouldUseManualMode ? "manual" : "guided"); + const [protocol, setProtocol] = useState(parsedPattern?.protocol ?? "https"); + const [subdomain, setSubdomain] = useState(parsedPattern?.subdomain ?? "any"); + const [domain, setDomain] = useState(parsedPattern?.domain ?? ""); + const [path, setPath] = useState(parsedPattern?.path ?? "any"); + const [customSubdomain, setCustomSubdomain] = useState(parsedPattern?.customSubdomain ?? ""); + const [customPath, setCustomPath] = useState(parsedPattern?.customPath ?? ""); + const [installedBrowsers, setInstalledBrowsers] = useState([]); + const [browserInputMode, setBrowserInputMode] = useState<"dropdown" | "manual">("dropdown"); + const [isLoadingBrowsers, setIsLoadingBrowsers] = useState(true); + + useEffect(() => { + async function loadBrowsers() { + try { + const browsers = await getInstalledBrowsers(); + setInstalledBrowsers(browsers); + } catch (error) { + console.error("Failed to detect browsers:", error); + } finally { + setIsLoadingBrowsers(false); + } + } + loadBrowsers(); + }, []); + + const generatePattern = () => { + if (!domain) return ""; + + const protocolPart = protocol === "any" ? "*" : protocol; + const subdomainPart = + subdomain === "any" ? "*" : subdomain === "none" ? "" : subdomain === "custom" ? customSubdomain : subdomain; + const domainPart = domain; + const pathPart = path === "any" ? "/*" : path === "none" ? "" : path === "custom" ? customPath : path; + + const host = subdomainPart ? `${subdomainPart}.${domainPart}` : domainPart; + return `${protocolPart}://${host}${pathPart}`; + }; + + const previewPattern = generatePattern(); + + return ( +
+ { + let patterns: string[]; + + if (inputMode === "manual") { + patterns = String(values.patterns ?? "") + .split("\n") + .map((s) => s.trim()) + .filter(Boolean); + } else { + const pattern = generatePattern(); + if (!pattern) { + await showToast({ + style: Toast.Style.Failure, + title: "Domain is required", + }); + return; + } + patterns = [pattern]; + } + + const rule: Rule = { + id: initial?.id ?? uuid(), + name: String(values.name ?? "").trim() || "Untitled Rule", + enabled: Boolean(values.enabled), + matchType: inputMode === "guided" ? "wildcards" : (values.matchType as Rule["matchType"]), + patterns, + browser: values.browser as string, + }; + + await props.onSubmit(rule); + pop(); + }} + /> + setInputMode(inputMode === "guided" ? "manual" : "guided")} + /> + setBrowserInputMode(browserInputMode === "dropdown" ? "manual" : "dropdown")} + /> + + } + > + + + + {inputMode === "manual" && ( + + + + + )} + + + + {inputMode === "guided" ? ( + <> + + + + + + + + + + + + + + + + {subdomain === "custom" && ( + + )} + + + + + + + + + + {path === "custom" && ( + + )} + + {previewPattern && } + + ) : ( + <> + + + + )} + + + + {browserInputMode === "dropdown" ? ( + <> + + {installedBrowsers.map((browser) => ( + + ))} + + {installedBrowsers.length > 0 && ( + + )} + + ) : ( + <> + + + + )} + + ); +} + +export default function Command() { + const [rules, setRules] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + + const { push } = useNavigation(); + + const sortedRules = useMemo(() => { + return [...rules].sort((a, b) => a.name.localeCompare(b.name)); + }, [rules]); + + async function refresh() { + setIsLoading(true); + const loaded = await loadRules(); + setRules(loaded); + setIsLoading(false); + } + + async function checkAndOfferImport() { + if (!configPath) return; + + const existingRules = await loadRules(); + if (existingRules.length > 0) return; + if (await isImportDismissed()) return; + + try { + const expandedPath = expandTilde(configPath); + await fs.access(expandedPath); + + const shouldImport = await confirmAlert({ + title: "Import Existing Rules?", + message: `Found existing Finicky config at ${configPath}. Would you like to import your rules?`, + primaryAction: { + title: "Import Rules", + style: Alert.ActionStyle.Default, + }, + dismissAction: { + title: "Start Fresh", + style: Alert.ActionStyle.Cancel, + }, + }); + + if (shouldImport) { + await importFromConfig(); + } else { + await setImportDismissed(true); + } + } catch { + // Config file doesn't exist, no import needed + } + } + + async function importFromConfig() { + if (!configPath) { + await showToast({ + style: Toast.Style.Failure, + title: "Set configPath in extension settings", + }); + return; + } + + try { + const expandedPath = expandTilde(configPath); + const parsed = await parseFinickyConfig(expandedPath); + + if (parsed.rules.length === 0) { + await showToast({ + style: Toast.Style.Failure, + title: "No rules found", + message: "Could not parse any rules from the config file", + }); + return; + } + if (parsed.defaultBrowser) { + await setDefaultBrowser(parsed.defaultBrowser); + } + + const shouldMerge = rules.length > 0; + let message = `Import ${parsed.rules.length} rule(s)?`; + + if (shouldMerge) { + const mergeConfirm = await confirmAlert({ + title: "Merge Imported Rules?", + message: `Found ${parsed.rules.length} rules in config. You have ${rules.length} existing rules.`, + primaryAction: { + title: "Merge (Keep Both)", + style: Alert.ActionStyle.Default, + }, + dismissAction: { + title: "Cancel", + style: Alert.ActionStyle.Cancel, + }, + }); + + if (mergeConfirm) { + const mergedRules = [...rules, ...parsed.rules]; + await persistAndSync(mergedRules, parsed.defaultBrowser); + message = `Merged ${parsed.rules.length} rules`; + } else { + const replaceConfirm = await confirmAlert({ + title: "Replace Existing Rules?", + message: `This will delete ${rules.length} existing rule(s).`, + primaryAction: { + title: "Replace", + style: Alert.ActionStyle.Destructive, + }, + dismissAction: { + title: "Cancel", + style: Alert.ActionStyle.Cancel, + }, + }); + if (!replaceConfirm) return; + + await persistAndSync(parsed.rules, parsed.defaultBrowser); + message = `Imported ${parsed.rules.length} rules`; + } + } else { + await persistAndSync(parsed.rules, parsed.defaultBrowser); + message = `Imported ${parsed.rules.length} rules`; + } + + await setImportDismissed(false); + + await showToast({ + style: Toast.Style.Success, + title: message, + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Import failed", + message: String(error), + }); + } + } + + useEffect(() => { + refresh().then(() => checkAndOfferImport()); + }, []); + + async function persistAndSync(nextRules: Rule[], defaultBrowserOverride?: string) { + if (!configPath) { + await showToast({ + style: Toast.Style.Failure, + title: "Set configPath in extension settings", + message: "Example: ~/.finicky.js", + }); + await saveRules(nextRules); + setRules(nextRules); + return; + } + + await saveRules(nextRules); + setRules(nextRules); + + try { + const defaultBrowser = defaultBrowserOverride ?? (await getDefaultBrowser()); + await syncConfigFile({ configPath, defaultBrowser, rules: nextRules }); + await showToast({ style: Toast.Style.Success, title: "Finicky config updated" }); + } catch (e) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to write config file", + message: String(e), + }); + } + } + + return ( + + { + push( + { + await persistAndSync([...rules, rule]); + }} + />, + ); + }} + /> + + + } + > + {sortedRules.map((rule) => ( + + { + push( + { + const next = rules.map((r) => (r.id === updated.id ? updated : r)); + await persistAndSync(next); + }} + />, + ); + }} + /> + + { + const next = rules.map((r) => (r.id === rule.id ? { ...r, enabled: !r.enabled } : r)); + await persistAndSync(next); + }} + /> + + { + push( + { + await persistAndSync([...rules, rule]); + }} + />, + ); + }} + /> + + { + const ok = await confirmAlert({ + title: "Delete rule?", + message: rule.name, + primaryAction: { + title: "Delete", + style: Alert.ActionStyle.Destructive, + }, + }); + if (!ok) return; + + const next = rules.filter((r) => r.id !== rule.id); + await persistAndSync(next); + }} + /> + + + + { + if (!configPath) { + await showToast({ + style: Toast.Style.Failure, + title: "Set configPath in extension settings", + }); + return; + } + await open(expandTilde(configPath)); + }} + /> + + { + await refresh(); + await showToast({ style: Toast.Style.Success, title: "Reloaded" }); + }} + /> + + popToRoot()} /> + + } + /> + ))} + + + { + push( + { + await persistAndSync([...rules, rule]); + }} + />, + ); + }} + /> + + + } + /> + + ); +} diff --git a/extensions/finicky-rule-manager/src/storage.ts b/extensions/finicky-rule-manager/src/storage.ts new file mode 100644 index 00000000000..e1e5512e1d4 --- /dev/null +++ b/extensions/finicky-rule-manager/src/storage.ts @@ -0,0 +1,32 @@ +import { LocalStorage, getPreferenceValues } from "@raycast/api"; +import { Rule } from "./types"; + +const STORAGE_KEY = "finickyRules.v1"; +const DEFAULT_BROWSER_KEY = "defaultBrowser"; + +export async function loadRules(): Promise { + const raw = await LocalStorage.getItem(STORAGE_KEY); + if (!raw) return []; + try { + const parsed = JSON.parse(raw) as Rule[]; + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +export async function saveRules(rules: Rule[]): Promise { + await LocalStorage.setItem(STORAGE_KEY, JSON.stringify(rules)); +} + +export async function getDefaultBrowser(): Promise { + const stored = await LocalStorage.getItem(DEFAULT_BROWSER_KEY); + if (stored) return stored; + + const preferences = getPreferenceValues<{ defaultBrowser?: string }>(); + return preferences.defaultBrowser || "Brave Browser"; +} + +export async function setDefaultBrowser(browser: string): Promise { + await LocalStorage.setItem(DEFAULT_BROWSER_KEY, browser); +} diff --git a/extensions/finicky-rule-manager/src/tools/create-rule.ts b/extensions/finicky-rule-manager/src/tools/create-rule.ts new file mode 100644 index 00000000000..e9dd0e3a6c9 --- /dev/null +++ b/extensions/finicky-rule-manager/src/tools/create-rule.ts @@ -0,0 +1,114 @@ +import { loadRules, saveRules, getDefaultBrowser } from "../storage"; +import { Rule } from "../types"; +import { generateFinickyConfig, writeConfigFile } from "../utils/finicky"; +import { expandTilde } from "../utils/path"; +import { getPreferenceValues } from "@raycast/api"; + +type Input = { + /** + * The name/description of the rule (e.g., "Google services", "Salesforce") + */ + name: string; + + /** + * The match type to use: "wildcards" or "regex" + */ + matchType: string; + + /** + * URL patterns as a comma-separated or newline-separated string + */ + patterns: string; + + /** + * The browser to open matching URLs in (e.g., "Arc", "Brave Browser", "Google Chrome", "Safari") + */ + browser: string; + + /** + * Whether the rule should be enabled immediately (default: true) + */ + enabled?: boolean; +}; + +function uuid(): string { + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +function normalizeMatchType(value: string): Rule["matchType"] { + const matchType = value.trim().toLowerCase(); + if (matchType === "wildcards" || matchType === "regex") return matchType; + throw new Error(`Invalid matchType "${value}". Use "wildcards" or "regex".`); +} + +function parsePatterns(value: string): string[] { + return value + .split(/[\n,]/) + .map((pattern) => pattern.trim()) + .filter(Boolean); +} + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string; rules: Rule[] }) { + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules: args.rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +/** + * Create a new Finicky rule with the specified patterns and browser + */ +export default async function tool(input: Input) { + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + const defaultBrowser = await getDefaultBrowser(); + const patterns = parsePatterns(input.patterns); + + if (patterns.length === 0) { + return "❌ Please provide at least one URL pattern."; + } + + const rule: Rule = { + id: uuid(), + name: input.name, + enabled: input.enabled ?? true, + matchType: normalizeMatchType(input.matchType), + patterns, + browser: input.browser, + }; + + const existingRules = await loadRules(); + + // Check for conflicts + const conflicts = existingRules.filter((existingRule) => { + if (!existingRule.enabled) return false; + + // Check if any patterns overlap + return existingRule.patterns.some((existingPattern) => + patterns.some((newPattern) => { + // Simple conflict detection: check if patterns are similar + if (existingRule.matchType === "wildcards" && input.matchType === "wildcards") { + // Extract domain from wildcard patterns + const existingDomain = existingPattern.match(/\/\/([^/]+)/)?.[1]?.replace(/^\*\./, ""); + const newDomain = newPattern.match(/\/\/([^/]+)/)?.[1]?.replace(/^\*\./, ""); + return existingDomain === newDomain; + } + // For regex, check if patterns are the same + return existingPattern === newPattern; + }), + ); + }); + + if (conflicts.length > 0) { + const conflictList = conflicts.map((c) => `- ${c.name}: ${c.patterns.join(", ")} → ${c.browser}`).join("\n"); + return `⚠️ Potential conflicts detected with existing rules:\n${conflictList}\n\nThe new rule "${input.name}" would match similar URLs. Do you want to:\n1. Create anyway (both rules will apply)\n2. Replace the conflicting rule(s)\n3. Cancel\n\nPlease specify how to handle this conflict.`; + } + + const updatedRules = [...existingRules, rule]; + await saveRules(updatedRules); + + if (configPath) { + await syncConfigFile({ configPath, defaultBrowser, rules: updatedRules }); + } + + return `✓ Successfully created rule "${input.name}"\n- Patterns: ${patterns.join(", ")}\n- Browser: ${input.browser}\n- Status: ${input.enabled ? "Enabled" : "Disabled"}`; +} diff --git a/extensions/finicky-rule-manager/src/tools/delete-rule.ts b/extensions/finicky-rule-manager/src/tools/delete-rule.ts new file mode 100644 index 00000000000..3e40d3a0f76 --- /dev/null +++ b/extensions/finicky-rule-manager/src/tools/delete-rule.ts @@ -0,0 +1,45 @@ +import { loadRules, saveRules, getDefaultBrowser } from "../storage"; +import { Rule } from "../types"; +import { generateFinickyConfig, writeConfigFile } from "../utils/finicky"; +import { expandTilde } from "../utils/path"; +import { getPreferenceValues } from "@raycast/api"; + +type Input = { + /** + * The ID or name of the rule to delete + */ + ruleIdentifier: string; +}; + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string; rules: Rule[] }) { + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules: args.rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +/** + * Delete a Finicky rule by ID or name + */ +export default async function tool(input: Input) { + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + const defaultBrowser = await getDefaultBrowser(); + + const rules = await loadRules(); + const ruleToDelete = rules.find( + (r) => r.id === input.ruleIdentifier || r.name.toLowerCase().includes(input.ruleIdentifier.toLowerCase()), + ); + + if (!ruleToDelete) { + return `❌ Rule not found: "${input.ruleIdentifier}". Use list-rules to see all available rules.`; + } + + const updatedRules = rules.filter((r) => r.id !== ruleToDelete.id); + await saveRules(updatedRules); + + if (configPath) { + await syncConfigFile({ configPath, defaultBrowser, rules: updatedRules }); + } + + return `✓ Successfully deleted rule "${ruleToDelete.name}"`; +} diff --git a/extensions/finicky-rule-manager/src/tools/find-conflicts.ts b/extensions/finicky-rule-manager/src/tools/find-conflicts.ts new file mode 100644 index 00000000000..a180090c18c --- /dev/null +++ b/extensions/finicky-rule-manager/src/tools/find-conflicts.ts @@ -0,0 +1,64 @@ +import { loadRules } from "../storage"; + +/** + * Find and list conflicting rules that match similar URL patterns + */ +export default async function tool() { + const rules = await loadRules(); + const enabledRules = rules.filter((r) => r.enabled); + + if (enabledRules.length < 2) { + return "No conflicts possible - you need at least 2 enabled rules to have conflicts."; + } + + const conflicts: Array<{ rule1: string; rule2: string; reason: string }> = []; + + for (let i = 0; i < enabledRules.length; i++) { + for (let j = i + 1; j < enabledRules.length; j++) { + const rule1 = enabledRules[i]; + const rule2 = enabledRules[j]; + + // Check for pattern overlaps + const overlappingPatterns: string[] = []; + + rule1.patterns.forEach((pattern1) => { + rule2.patterns.forEach((pattern2) => { + if (rule1.matchType === "wildcards" && rule2.matchType === "wildcards") { + // Extract domain from wildcard patterns + const domain1 = pattern1.match(/\/\/([^/]+)/)?.[1]?.replace(/^\*\./, ""); + const domain2 = pattern2.match(/\/\/([^/]+)/)?.[1]?.replace(/^\*\./, ""); + + if (domain1 && domain2 && domain1 === domain2) { + overlappingPatterns.push(`${pattern1} ≈ ${pattern2}`); + } + } else if (pattern1 === pattern2) { + overlappingPatterns.push(pattern1); + } + }); + }); + + if (overlappingPatterns.length > 0) { + conflicts.push({ + rule1: `${rule1.name} → ${rule1.browser}`, + rule2: `${rule2.name} → ${rule2.browser}`, + reason: `Overlapping patterns: ${overlappingPatterns.join(", ")}`, + }); + } + } + } + + if (conflicts.length === 0) { + return "✓ No conflicts found! All rules have unique patterns."; + } + + const conflictList = conflicts + .map( + (c, i) => `${i + 1}. Conflict between: + - ${c.rule1} + - ${c.rule2} + Reason: ${c.reason}`, + ) + .join("\n\n"); + + return `⚠️ Found ${conflicts.length} conflict(s):\n\n${conflictList}\n\nTo resolve conflicts, you can:\n- Disable one of the conflicting rules\n- Update patterns to be more specific\n- Delete duplicate rules`; +} diff --git a/extensions/finicky-rule-manager/src/tools/list-rules.ts b/extensions/finicky-rule-manager/src/tools/list-rules.ts new file mode 100644 index 00000000000..0dff5391b48 --- /dev/null +++ b/extensions/finicky-rule-manager/src/tools/list-rules.ts @@ -0,0 +1,26 @@ +import { loadRules } from "../storage"; + +/** + * List all Finicky rules with their details including name, patterns, browser, and enabled status + */ +export default async function tool() { + const rules = await loadRules(); + + if (rules.length === 0) { + return "No rules found. You can create a new rule by describing what you want."; + } + + const rulesList = rules + .map((rule, index) => { + const status = rule.enabled ? "✓ Enabled" : "✗ Disabled"; + const patterns = rule.patterns.join(", "); + return `${index + 1}. ${rule.name} (${status}) + - Match Type: ${rule.matchType} + - Patterns: ${patterns} + - Browser: ${rule.browser} + - ID: ${rule.id}`; + }) + .join("\n\n"); + + return `Found ${rules.length} rule(s):\n\n${rulesList}`; +} diff --git a/extensions/finicky-rule-manager/src/tools/update-rule.ts b/extensions/finicky-rule-manager/src/tools/update-rule.ts new file mode 100644 index 00000000000..ab9019559bf --- /dev/null +++ b/extensions/finicky-rule-manager/src/tools/update-rule.ts @@ -0,0 +1,99 @@ +import { loadRules, saveRules, getDefaultBrowser } from "../storage"; +import { Rule } from "../types"; +import { generateFinickyConfig, writeConfigFile } from "../utils/finicky"; +import { expandTilde } from "../utils/path"; +import { getPreferenceValues } from "@raycast/api"; + +type Input = { + /** + * The ID or name of the rule to update + */ + ruleIdentifier: string; + + /** + * New name for the rule (optional) + */ + name?: string; + + /** + * New patterns as a comma-separated or newline-separated string (optional) + */ + patterns?: string; + + /** + * New browser (optional) + */ + browser?: string; + + /** + * Enable or disable the rule (optional) + */ + enabled?: boolean; +}; + +function parsePatterns(value: string): string[] { + return value + .split(/[\n,]/) + .map((pattern) => pattern.trim()) + .filter(Boolean); +} + +async function syncConfigFile(args: { configPath: string; defaultBrowser: string; rules: Rule[] }) { + const configPath = expandTilde(args.configPath); + const contents = generateFinickyConfig({ defaultBrowser: args.defaultBrowser, rules: args.rules }); + await writeConfigFile({ configPath, configContents: contents }); +} + +/** + * Update an existing Finicky rule's properties + */ +export default async function tool(input: Input) { + const preferences = getPreferenceValues<{ configPath?: string }>(); + const configPath = (preferences.configPath ?? "").trim(); + const defaultBrowser = await getDefaultBrowser(); + + const rules = await loadRules(); + const ruleIndex = rules.findIndex( + (r) => r.id === input.ruleIdentifier || r.name.toLowerCase().includes(input.ruleIdentifier.toLowerCase()), + ); + + if (ruleIndex === -1) { + return `❌ Rule not found: "${input.ruleIdentifier}". Use list-rules to see all available rules.`; + } + + const rule = rules[ruleIndex]; + const updates: string[] = []; + + if (input.name !== undefined) { + rule.name = input.name; + updates.push(`name → "${input.name}"`); + } + + if (input.patterns !== undefined) { + const patterns = parsePatterns(input.patterns); + if (patterns.length === 0) { + return "❌ Please provide at least one URL pattern when updating patterns."; + } + rule.patterns = patterns; + updates.push(`patterns → ${patterns.join(", ")}`); + } + + if (input.browser !== undefined) { + rule.browser = input.browser; + updates.push(`browser → ${input.browser}`); + } + + if (input.enabled !== undefined) { + rule.enabled = input.enabled; + updates.push(`status → ${input.enabled ? "Enabled" : "Disabled"}`); + } + + rules[ruleIndex] = rule; + await saveRules(rules); + + if (configPath) { + await syncConfigFile({ configPath, defaultBrowser, rules }); + } + + return `✓ Successfully updated rule "${rule.name}"\nChanges:\n${updates.map((u) => `- ${u}`).join("\n")}`; +} diff --git a/extensions/finicky-rule-manager/src/types.ts b/extensions/finicky-rule-manager/src/types.ts new file mode 100644 index 00000000000..1546dae8ae9 --- /dev/null +++ b/extensions/finicky-rule-manager/src/types.ts @@ -0,0 +1,10 @@ +export type MatchType = "wildcards" | "regex"; + +export type Rule = { + id: string; + name: string; + enabled: boolean; + matchType: MatchType; + patterns: string[]; + browser: string; +}; diff --git a/extensions/finicky-rule-manager/src/utils/browsers.ts b/extensions/finicky-rule-manager/src/utils/browsers.ts new file mode 100644 index 00000000000..8e310986836 --- /dev/null +++ b/extensions/finicky-rule-manager/src/utils/browsers.ts @@ -0,0 +1,79 @@ +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export interface Browser { + name: string; + bundleId: string; + homepage?: string; + appPath?: string; +} + +const COMMON_BROWSERS: Browser[] = [ + { name: "Safari", bundleId: "com.apple.Safari", homepage: "https://www.apple.com/safari/" }, + { name: "Google Chrome", bundleId: "com.google.Chrome", homepage: "https://www.google.com/chrome/" }, + { name: "Brave Browser", bundleId: "com.brave.Browser", homepage: "https://brave.com" }, + { name: "Arc", bundleId: "company.thebrowser.Browser", homepage: "https://arc.net" }, + { name: "Firefox", bundleId: "org.mozilla.firefox", homepage: "https://www.mozilla.org/firefox/" }, + { name: "Microsoft Edge", bundleId: "com.microsoft.edgemac", homepage: "https://www.microsoft.com/edge" }, + { name: "Opera", bundleId: "com.operasoftware.Opera", homepage: "https://www.opera.com" }, + { name: "Vivaldi", bundleId: "com.vivaldi.Vivaldi", homepage: "https://vivaldi.com" }, + { name: "Chromium", bundleId: "org.chromium.Chromium", homepage: "https://www.chromium.org" }, + { + name: "Safari Technology Preview", + bundleId: "com.apple.SafariTechnologyPreview", + homepage: "https://developer.apple.com/safari/", + }, + { name: "DuckDuckGo", bundleId: "com.duckduckgo.macos.browser", homepage: "https://duckduckgo.com" }, + { name: "Orion", bundleId: "com.kagi.kagimacOS", homepage: "https://browser.kagi.com" }, + { name: "SigmaOS", bundleId: "com.sigmaos.sigmaos.macos", homepage: "https://sigmaos.com" }, +]; + +/** + * Detects installed browsers on macOS + */ +export async function detectInstalledBrowsers(): Promise { + const installed: Browser[] = []; + + for (const browser of COMMON_BROWSERS) { + try { + // Use mdfind to check if the app is installed and get its path + const { stdout } = await execAsync(`mdfind "kMDItemCFBundleIdentifier == '${browser.bundleId}'"`); + const appPath = stdout.trim().split("\n")[0]; // Get first result + if (appPath) { + installed.push({ ...browser, appPath }); + } + } catch { + // Browser not found, skip + } + } + + return installed; +} + +/** + * Gets a cached list of browsers or detects them + */ +let cachedBrowsers: Browser[] | null = null; +let cacheUpdatedAt = 0; +const BROWSER_CACHE_TTL_MS = 60 * 1000; + +export async function getInstalledBrowsers(): Promise { + const cacheIsFresh = Date.now() - cacheUpdatedAt < BROWSER_CACHE_TTL_MS; + if (cachedBrowsers && cacheIsFresh) { + return cachedBrowsers; + } + + cachedBrowsers = await detectInstalledBrowsers(); + cacheUpdatedAt = Date.now(); + return cachedBrowsers; +} + +/** + * Clears the browser cache (useful for testing or refresh) + */ +export function clearBrowserCache(): void { + cachedBrowsers = null; + cacheUpdatedAt = 0; +} diff --git a/extensions/finicky-rule-manager/src/utils/finicky.ts b/extensions/finicky-rule-manager/src/utils/finicky.ts new file mode 100644 index 00000000000..6e1da473c8e --- /dev/null +++ b/extensions/finicky-rule-manager/src/utils/finicky.ts @@ -0,0 +1,63 @@ +import fs from "fs/promises"; +import { Rule } from "../types"; + +function ruleToHandler(rule: Rule): string { + const browser = JSON.stringify(rule.browser); + + if (rule.matchType === "regex") { + const sources = rule.patterns.map((p) => p.trim()).filter(Boolean); + + if (sources.length === 0) return ""; + + const combined = sources.length === 1 ? sources[0] : `(?:${sources.join(")|(?:")})`; + + return `{ + match: ({ urlString }) => new RegExp(${JSON.stringify(combined)}, "i").test(urlString), + browser: ${browser} +}`; + } + + const patterns = rule.patterns + .map((p) => p.trim()) + .filter(Boolean) + .map((p) => JSON.stringify(p)) + .join(",\n "); + + if (!patterns) return ""; + + return `{ + match: [ + ${patterns} + ], + browser: ${browser} +}`; +} + +export function generateFinickyConfig(params: { defaultBrowser: string; rules: Rule[] }): string { + const enabledRules = params.rules.filter((r) => r.enabled); + + const handlers = enabledRules + .map((r) => { + const handler = ruleToHandler(r); + if (!handler) return ""; + return `// Rule: ${r.name}\n${handler}`; + }) + .filter(Boolean) + .join(",\n\n "); + + return `// Generated by Raycast: Finicky Rules Manager +// Do not edit by hand (your changes may be overwritten). + +export default { + defaultBrowser: ${JSON.stringify(params.defaultBrowser)}, + + handlers: [ + ${handlers} + ] +}; +`; +} + +export async function writeConfigFile(args: { configPath: string; configContents: string }) { + await fs.writeFile(args.configPath, args.configContents, { encoding: "utf8" }); +} diff --git a/extensions/finicky-rule-manager/src/utils/parser.ts b/extensions/finicky-rule-manager/src/utils/parser.ts new file mode 100644 index 00000000000..e4b93c80f1f --- /dev/null +++ b/extensions/finicky-rule-manager/src/utils/parser.ts @@ -0,0 +1,384 @@ +import fs from "fs/promises"; +import { Rule } from "../types"; + +function uuid(): string { + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +export interface ParsedConfig { + rules: Rule[]; + defaultBrowser?: string; +} + +export async function parseFinickyConfig(configPath: string): Promise { + try { + const content = await fs.readFile(configPath, "utf8"); + return parseFinickyConfigContent(content); + } catch (error) { + throw new Error(`Failed to read config file: ${error}`); + } +} + +/** + * Robustly parse a Finicky config that looks like: + * module.exports = { defaultBrowser: "...", handlers: [ { ... }, { ... } ] } + * + * Key goals: + * - Do NOT use regex to capture the whole handlers array (it breaks on inner match arrays). + * - Instead, locate "handlers: [" and then scan until the matching closing ']'. + * - Split top-level handler objects by brace-depth. + */ +export function parseFinickyConfigContent(content: string): ParsedConfig { + const rules: Rule[] = []; + let defaultBrowser: string | undefined; + + try { + const defaultBrowserMatch = content.match(/defaultBrowser\s*:\s*["']([^"']+)["']/); + if (defaultBrowserMatch) { + defaultBrowser = defaultBrowserMatch[1]; + } + + const handlersContent = extractHandlersArrayContent(content); + if (!handlersContent) { + return { rules, defaultBrowser }; + } + + const handlerBlocks = splitTopLevelObjects(handlersContent); + + for (const block of handlerBlocks) { + const rule = parseHandlerBlock(block); + if (rule) { + rules.push(rule); + } + } + } catch (error) { + console.error("Error parsing Finicky config:", error); + } + + return { rules, defaultBrowser }; +} + +function extractHandlersArrayContent(content: string): string | null { + // Find "handlers" then the first '[' after it, then scan to its matching ']'. + const handlersKeyIndex = content.search(/\bhandlers\s*:/); + if (handlersKeyIndex === -1) return null; + + const openBracketIndex = content.indexOf("[", handlersKeyIndex); + if (openBracketIndex === -1) return null; + + const closeBracketIndex = findMatchingBracket(content, openBracketIndex, "[", "]"); + if (closeBracketIndex === -1) return null; + + return content.slice(openBracketIndex + 1, closeBracketIndex); +} + +function findMatchingBracket(text: string, openIndex: number, openChar: string, closeChar: string): number { + let depth = 0; + let inSingle = false; + let inDouble = false; + let inTemplate = false; + let inRegex = false; + let escaping = false; + + for (let i = openIndex; i < text.length; i++) { + const c = text[i]; + const prev = i > 0 ? text[i - 1] : ""; + + if (escaping) { + escaping = false; + continue; + } + + // Escape handling inside strings/templates/regex + if (c === "\\") { + escaping = true; + continue; + } + + // Toggle string/template states (but not inside regex) + if (!inRegex) { + if (!inDouble && !inTemplate && c === "'" && !inSingle) { + inSingle = true; + continue; + } else if (inSingle && c === "'") { + inSingle = false; + continue; + } + + if (!inSingle && !inTemplate && c === '"' && !inDouble) { + inDouble = true; + continue; + } else if (inDouble && c === '"') { + inDouble = false; + continue; + } + + if (!inSingle && !inDouble && c === "`" && !inTemplate) { + inTemplate = true; + continue; + } else if (inTemplate && c === "`") { + inTemplate = false; + continue; + } + } + + // Very light regex literal detection: + // If we see a '/' that looks like it could start a regex and we aren't in a string/template. + // This isn't perfect, but good enough for typical Finicky configs. + if (!inSingle && !inDouble && !inTemplate) { + if (!inRegex && c === "/" && prev !== "/" && prev !== "*") { + // Heuristic: treat as regex start only if previous non-space char is one of these + const prevNonSpace = findPrevNonSpace(text, i - 1); + if (prevNonSpace === "" || /[=(:[{},!&|?;]/.test(prevNonSpace)) { + inRegex = true; + continue; + } + } else if (inRegex && c === "/") { + inRegex = false; + continue; + } + } + + // If inside any literal, ignore structural chars + if (inSingle || inDouble || inTemplate || inRegex) continue; + + if (c === openChar) depth++; + if (c === closeChar) { + depth--; + if (depth === 0) return i; + } + } + + return -1; +} + +function findPrevNonSpace(text: string, fromIndex: number): string { + for (let i = fromIndex; i >= 0; i--) { + const c = text[i]; + if (!/\s/.test(c)) return c; + } + return ""; +} + +function splitTopLevelObjects(arrayContent: string): string[] { + const blocks: string[] = []; + + let depth = 0; + let start = -1; + + let inSingle = false; + let inDouble = false; + let inTemplate = false; + let inRegex = false; + let escaping = false; + + for (let i = 0; i < arrayContent.length; i++) { + const c = arrayContent[i]; + const prev = i > 0 ? arrayContent[i - 1] : ""; + + if (escaping) { + escaping = false; + continue; + } + if (c === "\\") { + escaping = true; + continue; + } + + if (!inRegex) { + if (!inDouble && !inTemplate && c === "'" && !inSingle) { + inSingle = true; + continue; + } else if (inSingle && c === "'") { + inSingle = false; + continue; + } + + if (!inSingle && !inTemplate && c === '"' && !inDouble) { + inDouble = true; + continue; + } else if (inDouble && c === '"') { + inDouble = false; + continue; + } + + if (!inSingle && !inDouble && c === "`" && !inTemplate) { + inTemplate = true; + continue; + } else if (inTemplate && c === "`") { + inTemplate = false; + continue; + } + } + + if (!inSingle && !inDouble && !inTemplate) { + if (!inRegex && c === "/" && prev !== "/" && prev !== "*") { + const prevNonSpace = findPrevNonSpace(arrayContent, i - 1); + if (prevNonSpace === "" || /[=(:[{},!&|?;]/.test(prevNonSpace)) { + inRegex = true; + continue; + } + } else if (inRegex && c === "/") { + inRegex = false; + continue; + } + } + + if (inSingle || inDouble || inTemplate || inRegex) continue; + + if (c === "{") { + if (depth === 0) start = i; + depth++; + continue; + } + + if (c === "}") { + depth--; + if (depth === 0 && start !== -1) { + blocks.push(arrayContent.slice(start, i + 1)); + start = -1; + } + continue; + } + } + + return blocks; +} + +/** + * Extract quoted string literals from the inside of an array like + * "a", "b[0-9]", 'c' + * Respects escape sequences and matched quote types so patterns containing + * commas or ']' characters are preserved verbatim. + */ +function splitTopLevelStrings(arrayContent: string): string[] { + const out: string[] = []; + let i = 0; + while (i < arrayContent.length) { + const c = arrayContent[i]; + if (c === '"' || c === "'") { + const quote = c; + let value = ""; + i++; // skip opening quote + while (i < arrayContent.length) { + const ch = arrayContent[i]; + if (ch === "\\" && i + 1 < arrayContent.length) { + value += arrayContent[i + 1]; + i += 2; + continue; + } + if (ch === quote) { + i++; // skip closing quote + break; + } + value += ch; + i++; + } + out.push(value); + continue; + } + i++; + } + return out; +} + +function parseHandlerBlock(block: string): Rule | null { + try { + const nameMatch = block.match(/\/\/\s*(?:Rule:\s*)?([^\n]+)/); + const name = nameMatch ? nameMatch[1].trim() : "Imported Rule"; + + // Support both browser: "App" and browser: { name: "App", ... } + const browserMatch = block.match(/browser\s*:\s*["']([^"']+)["']/); + let browser = browserMatch?.[1]; + + if (!browser) { + const browserObjectMatch = /\bbrowser\s*:\s*\{/.exec(block); + if (browserObjectMatch) { + const openBraceIndex = block.indexOf("{", browserObjectMatch.index); + const closeBraceIndex = findMatchingBracket(block, openBraceIndex, "{", "}"); + if (openBraceIndex !== -1 && closeBraceIndex !== -1) { + const browserObjectContent = block.slice(openBraceIndex + 1, closeBraceIndex); + const browserNameMatch = browserObjectContent.match(/name\s*:\s*["']([^"']+)["']/); + if (browserNameMatch) { + browser = browserNameMatch[1]; + } + } + } + } + + if (!browser) { + return null; + } + + const singleStringMatch = block.match(/\bmatch\s*:\s*["']([^"']+)["']/); + if (singleStringMatch) { + return { + id: uuid(), + name, + enabled: true, + matchType: "wildcards", + patterns: [singleStringMatch[1]], + browser, + }; + } + + // match: [ "a", "b" ] — use bracket-aware scanning so patterns containing + // ']' (e.g. "*://example.com/path[0-9]*") aren't truncated. + const matchKeyRegex = /\bmatch\s*:\s*\[/; + const matchKeyResult = matchKeyRegex.exec(block); + if (matchKeyResult) { + const openBracketIndex = block.indexOf("[", matchKeyResult.index); + const closeBracketIndex = findMatchingBracket(block, openBracketIndex, "[", "]"); + if (openBracketIndex !== -1 && closeBracketIndex !== -1) { + const patternsContent = block.slice(openBracketIndex + 1, closeBracketIndex); + const patterns = splitTopLevelStrings(patternsContent); + + if (patterns.length > 0) { + return { + id: uuid(), + name, + enabled: true, + matchType: "wildcards", + patterns, + browser, + }; + } + } + } + + // match: (...) => new RegExp("...", "i").test(...) + const matchFunctionMatch = block.match( + /match\s*:\s*(?:\([^)]*\)\s*=>|function\s*\([^)]*\))\s*(?:new\s+RegExp\s*\(\s*["']([^"']+)["'])/, + ); + if (matchFunctionMatch) { + const regexPattern = matchFunctionMatch[1]; + return { + id: uuid(), + name, + enabled: true, + matchType: "regex", + patterns: [regexPattern], + browser, + }; + } + + // match: ({ urlString }) => /.../i.test(urlString) + const matchTestMatch = block.match(/\/([^/]+)\/[igm]*\.test\(/); + if (matchTestMatch) { + const regexPattern = matchTestMatch[1]; + return { + id: uuid(), + name, + enabled: true, + matchType: "regex", + patterns: [regexPattern], + browser, + }; + } + + return null; + } catch (error) { + console.error("Error parsing handler block:", error); + return null; + } +} diff --git a/extensions/finicky-rule-manager/src/utils/path.ts b/extensions/finicky-rule-manager/src/utils/path.ts new file mode 100644 index 00000000000..c332b7d5904 --- /dev/null +++ b/extensions/finicky-rule-manager/src/utils/path.ts @@ -0,0 +1,9 @@ +import path from "path"; +import os from "os"; + +export function expandTilde(inputPath: string): string { + if (!inputPath) return inputPath; + if (inputPath === "~") return os.homedir(); + if (inputPath.startsWith("~/")) return path.join(os.homedir(), inputPath.slice(2)); + return inputPath; +} diff --git a/extensions/finicky-rule-manager/tsconfig.json b/extensions/finicky-rule-manager/tsconfig.json new file mode 100644 index 00000000000..62e9ff19550 --- /dev/null +++ b/extensions/finicky-rule-manager/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*", "raycast-env.d.ts"], + "compilerOptions": { + "lib": ["ES2022"], + "module": "commonjs", + "types": ["node"], + "target": "ES2022", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "resolveJsonModule": true + } +}