diff --git a/README.md b/README.md index 6052daa8..ec544a45 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,9 @@ Defi is in alpha, so behavior and configuration may still change. - **Smooth motion**: windows glide on an overlay, then move once underneath. - **App rules**: send apps to a workspace, float them, or set their width. - **Keyboard and mouse**: custom shortcuts with a cheatsheet, and drag to reorder. -- **Scriptable**: live TOML config, a CLI, and a - [SketchyBar integration](contrib/sketchybar/README.md). +- **Scriptable**: live TOML config, a CLI, a + [SketchyBar integration](contrib/sketchybar/README.md), and a + [Raycast extension](contrib/raycast/README.md). Window borders, gaps, and every other option are in the [configuration reference](CONFIGURATION.md). diff --git a/contrib/raycast/.gitignore b/contrib/raycast/.gitignore new file mode 100644 index 00000000..b6fce099 --- /dev/null +++ b/contrib/raycast/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +raycast-env.d.ts diff --git a/contrib/raycast/.prettierrc b/contrib/raycast/.prettierrc new file mode 100644 index 00000000..fc0f5030 --- /dev/null +++ b/contrib/raycast/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 120, + "singleQuote": false +} diff --git a/contrib/raycast/CHANGELOG.md b/contrib/raycast/CHANGELOG.md new file mode 100644 index 00000000..d9944ce7 --- /dev/null +++ b/contrib/raycast/CHANGELOG.md @@ -0,0 +1,7 @@ +# Defi Changelog + +## [Initial Version] - {PR_MERGE_DATE} + +- Search Windows: focus any managed window, bring it to the current workspace, move it, float it, or copy its title or ID. +- Search Workspaces: jump to a workspace on any display, or move the focused window or column there. +- Search Commands: run any Defi command; the search text sets widths and heights and targets workspaces. diff --git a/contrib/raycast/README.md b/contrib/raycast/README.md new file mode 100644 index 00000000..32657497 --- /dev/null +++ b/contrib/raycast/README.md @@ -0,0 +1,61 @@ +# Defi + +Reach [Defi](https://github.com/qeude/Defi), the scrolling-columns window +manager for macOS, from Raycast: switch windows, jump between workspaces, and +run any Defi command. The extension only runs the `defi` command line tool +inside Defi.app and makes no network calls, so window titles stay on your Mac. + +Requires [Defi.app](https://github.com/qeude/Defi/releases/latest) in +`/Applications` or `~/Applications`, or picked in the extension's preferences. + +## Commands + +**Search Windows** finds any window Defi manages by title, app, or workspace; +the dropdown narrows it to one workspace. Windows you pick often come first, +and the focused one comes last. Acting on a window from another workspace shows +that workspace for a moment, because Defi focuses the window first. + +| Key | Action | +| --- | --- | +| ↵ | Focus the window | +| ⌘↵ | Pick a workspace to move it to | +| ⇧↵ | Bring it to the current workspace | +| ⌘⇧F | Float or tile it | +| ⌘⇧C / ⌘⇧I | Copy its title or ID | + +**Search Workspaces** lists the workspaces of every display with their window +count and apps; search by name, number, or app. + +| Key | Action | +| --- | --- | +| ↵ | Go to the workspace | +| ⌘↵ / ⌘⇧↵ | Move the focused window there, or send it without following | +| ⌥↵ / ⌥⇧↵ | Move or send the focused column | +| ⌘O | List the workspace's windows | + +**Search Commands** runs any Defi command, with the ones you ran last on top. +The side pane says what each one does, its default shortcut, and the line to +paste into `[keys]`. The search text also works as an argument: + +| Type | To | +| --- | --- | +| `60`, `+10`, `-10`, `960` | Set or change the column width, in percent or points | +| `h50`, `h+10` | Set or change a stacked window's height | +| `2`, `web` | Go to a workspace, or move the focused window or column there | + +Type the same text after the command in the root search, as in +`Search Commands 60`. ⌘↵ copies a command in the form `[keys]` accepts. See +[Commands](https://github.com/qeude/Defi/blob/main/CONFIGURATION.md#commands). + +In every list, ⌘Y shows or hides the side pane and ⌘R reloads. Shortcuts shown +are Defi's defaults, with `mod` as ⌃⌥; your own `[keys]` may differ. Give Search +Windows a hotkey or an alias such as `w` to reach it in one keystroke. + +## Troubleshooting + +- **Defi Is Not Installed**: install Defi, or pick Defi.app in **Defi App** in + the extension's preferences. +- **Defi Is Not Running**: press Enter to launch it; the list fills in once + Defi starts. +- **Update Defi** or **Update This Extension**: Defi and the extension read + different versions of the JSON format. Update the older one. diff --git a/contrib/raycast/assets/icon.png b/contrib/raycast/assets/icon.png new file mode 100644 index 00000000..0b746a67 Binary files /dev/null and b/contrib/raycast/assets/icon.png differ diff --git a/contrib/raycast/assets/icon@dark.png b/contrib/raycast/assets/icon@dark.png new file mode 100644 index 00000000..4b0bb7dd Binary files /dev/null and b/contrib/raycast/assets/icon@dark.png differ diff --git a/contrib/raycast/eslint.config.mjs b/contrib/raycast/eslint.config.mjs new file mode 100644 index 00000000..a1770964 --- /dev/null +++ b/contrib/raycast/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/contrib/raycast/package-lock.json b/contrib/raycast/package-lock.json new file mode 100644 index 00000000..d942ed82 --- /dev/null +++ b/contrib/raycast/package-lock.json @@ -0,0 +1,3592 @@ +{ + "name": "defi", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "defi", + "license": "MIT", + "dependencies": { + "@raycast/api": "^2.11.1", + "@raycast/utils": "^2.3.3" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.2.0", + "@types/node": "26.6.5", + "@types/react": "19.3.0", + "eslint": "^10.0.1", + "prettier": "^3.8.1", + "typescript": "^5.9.3" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "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.3", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.3.tgz", + "integrity": "sha512-R76UKbvsqI68DJJVTVyqFKqU98CN4+B+acxNfEA2IFarSzP0SjtKf/3+fFiXzTyv5Fj3PxLkGHe2wnVWWMxobQ==", + "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-array/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "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.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "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.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "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.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.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/@isaacs/nonrecursive-glob-match": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.1.0.tgz", + "integrity": "sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@oclif/core": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-5.1.12.tgz", + "integrity": "sha512-t8W/ljOb9DIsFNXbfLU4UpZ8+C/X0QuvJOuQXQVCthiw5zSdwKnKoq7HmyBUJwwo28302eiN0rRj8vN9k1U1Mw==", + "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": "^6", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "lilconfig": "^3.1.3", + "minimatch": "^11.0.1", + "semver": "^7.8.1", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.17", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0", + "wsl-utils": "^0.4.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-4.0.6.tgz", + "integrity": "sha512-TCdtU3jMG2OJPTIUu9a4qpG/CW6t0ymbk4/GdLfc/o2455AhdYJphIaIpNe772xFhcqceVDg1kWUB5lacDaw2w==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^5.1.9", + "ansis": "^3.16.0", + "debug": "^4.4.1", + "ejs": "^3.1.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete/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/@oclif/plugin-help": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-7.0.7.tgz", + "integrity": "sha512-k1mWE1bp3AhO6MIKRpdgbGKiPORNCikYRhpGCM7kIz0ywn72/vijoSPOxTfZRbVZyJKFtR25rZ9Lj+QGG2aBNQ==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^5.1.11" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@oclif/plugin-not-found": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-4.0.7.tgz", + "integrity": "sha512-i1SKG/rnCx/uy2YYzo3mthouiV5sFC+LXbudwRbjDgHEY87uSGPQG02kRYuge5s5XDmn0c8FOqzu6IVbx1aV5w==", + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.10.1", + "@oclif/core": "^5.1.9", + "ansis": "^3.17.0", + "fast-levenshtein": "^3.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@raycast/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-2.11.1.tgz", + "integrity": "sha512-O9tCR9QlCa0BhSidkkcNHHii+Y4H8nUO/MRkwpQMXsW0Z/jgSh+GsjG0QeeKS4WHbyhcFPWr7lPbTcr27pRo9A==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^5.1.9", + "@oclif/plugin-autocomplete": "^4.0.5", + "@oclif/plugin-help": "^7.0.5", + "@oclif/plugin-not-found": "^4.0.5", + "@types/node": "26.6.5", + "@types/react": "19.3.0", + "clipboardy": "^5.3.2", + "esbuild": "^0.28.2", + "open": "^11.0.4", + "react": "19.3.1" + }, + "bin": { + "ray": "bin/run.js" + }, + "engines": { + "node": ">=22.22.2" + }, + "peerDependencies": { + "@types/node": "26.6.5", + "@types/react": "19.3.0", + "react-devtools": "8.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react-devtools": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-config": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@raycast/eslint-config/-/eslint-config-2.2.0.tgz", + "integrity": "sha512-uS/UDvM+3HfABgYhLYnIv0y+IVu+rGiZZIMwQsO5EGrcf2/TX/yioTzfezAQqhdszW4ViCuHbv8BddIagDKZvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/js": "^9.39.4", + "@raycast/eslint-plugin": "^2.2.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.7.0", + "typescript-eslint": "^8.62.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "prettier": ">=2", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@raycast/eslint-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@raycast/eslint-plugin/-/eslint-plugin-2.2.0.tgz", + "integrity": "sha512-ixO3PoRAEAZZcSQeRPvvDS/vs09D47tXdyXrs7hctoxxPtweXgJ6I24kjCD5GuuuQS5hGTiuL+Uy6hIg6a/a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.62.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@raycast/utils": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.3.3.tgz", + "integrity": "sha512-qtnuoRijFD+NDC4XEgoAyrmuIuqwYtl73RYPwuk/drhRNp0ywktNbb7wI58ZM9vPj+253iF/F5tr0TYwTuz0Fg==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "@raycast/api": ">=1.99.4", + "react": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "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": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.2.tgz", + "integrity": "sha512-222PKFXKihmFpi/pS5njoBgdAqsPhj2/GBmFbrNw8Tq8gEG9Sv+kSlKFy4u2q+mJv9ZzJgM2Gkv/bUzRZwZw5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/type-utils": "8.72.2", + "@typescript-eslint/utils": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", + "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.72.2", + "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.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.2.tgz", + "integrity": "sha512-llP+T1YuJOLlxEciP6OyQGtz5SMxyn+9LA2BvZw5RmToGVa1y4beEDVQL6Gje7T5WFYh19WREjDCN9Ev4gm5AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.2.tgz", + "integrity": "sha512-6y5dLTZdHhI8Eh3yIQ/bfQhRKwn5e+TpJj64o18Cngc0iW/BCrVvrr6zGYqXPnYH31u6YXAYiFh0+qxX5mybJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.72.2", + "@typescript-eslint/types": "^8.72.2", + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.2.tgz", + "integrity": "sha512-iIoMJ6/8c1/QpFNyFjU0d1YzPco4MsZjQw0k3V+vfzNWAFmUmvhlyMoYEoYYDQZXc+J6Gi1X5XcxlzW2taIIGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2" + }, + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.2.tgz", + "integrity": "sha512-+h3iCgZXuTNtoLXcnnjP12ZUHLMqoUcIHPfQrFEXTUqvb9dF8QK9b5nv5VtSo5fLkdHuks8ehbTzZj3rImAm9w==", + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.2.tgz", + "integrity": "sha512-STfkR38qIdhfkLpgtXnUjAzsNy4xouqiBO0lrVc/Wpk9Kem5/aHXUx1J8dGC16zpdYN1+yc2ILQ28er1kXX7ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/utils": "8.72.2", + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.2.tgz", + "integrity": "sha512-Xfn37OLZx03+PCU7qWx9DTv2FCRh1E6fk2+wONeNY15HXIwtcwuveaSwVpGAUptkNmPQegRxW5JyRel9T7wrog==", + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.2.tgz", + "integrity": "sha512-pQEGqIK/cqEvKivVWaFLYpe7XYejFxfuAOi4mu1oAWFvIo36aRHI9esRFTUxcIy/z8N482RJyH+LpZB0ZIU7Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.72.2", + "@typescript-eslint/tsconfig-utils": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", + "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/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.2.tgz", + "integrity": "sha512-hv3eRYbpPbbpezuax1EfGrzpa3C4ySgNZkPUkLOWOAjU8HsOKowEDAr8C7nsNl1C+SBSKDpvno6VKHnlhvp9iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2" + }, + "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.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.2.tgz", + "integrity": "sha512-NAGrjn0Pitjqzq/GkgMC4kBYm233PQ8GffyQ9+kTA3AlNQYEzOf25EIM02BAHZv5FRJSpHpJk+OGUxVkOB93GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.72.2", + "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.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.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.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "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/clipboard-image": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clipboard-image/-/clipboard-image-0.1.0.tgz", + "integrity": "sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==", + "license": "MIT", + "dependencies": { + "run-jxa": "^3.0.0" + }, + "bin": { + "clipboard-image": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.3.2.tgz", + "integrity": "sha512-R35PENCHFCw6lsd5SjYPuAVV3Zawr74mKc7ogFNzoDPoQsmWDoJgUNNnWCk/czeqdZGZs8Y0M8zkOlVoySfHEQ==", + "license": "MIT", + "dependencies": { + "clipboard-image": "^0.1.0", + "execa": "^9.6.1", + "is-wayland": "^0.1.0", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0", + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", + "license": "Apache-2.0", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "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.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "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.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.14.0.tgz", + "integrity": "sha512-T0pNsxkb9IKmtQjhpK3d7nWOpFv+YHuMbTJnSVMoQblF0onvXfErrtp4V1jHtsQPan7dKaGVD0q1WCXnzMF31A==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.8", + "@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": "11.1.5 || >11.1.6 <12", + "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.6", + "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/eslint/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "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.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "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": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "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/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "17.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.14.0.tgz", + "integrity": "sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==", + "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/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "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-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wayland": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-wayland/-/is-wayland-0.1.0.tgz", + "integrity": "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "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-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": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.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.5", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.5.tgz", + "integrity": "sha512-aRAOI9Ia43ygL4o1EOmC/sc/0OFxrm7qcQngiIyWaqVnSnz932ybJ07zyCcgMNGSDP8Yxaf88Xj1zHEXmvQ4SA==", + "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/macos-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/macos-version/-/macos-version-6.0.0.tgz", + "integrity": "sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "11.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.8.tgz", + "integrity": "sha512-nFQuYSTVrGvysVBps5Ih+0BdnYFN/Cj3C816r6+p7yK+FHrVLGE4O4N9AFwykrlNlvM2HYt+Rad1Uiro6h88Ig==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/nonrecursive-glob-match": "^1.1.0", + "brace-expansion": "^5.0.12" + }, + "engines": { + "node": "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/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/wsl-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", + "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-jxa/-/run-jxa-3.0.0.tgz", + "integrity": "sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==", + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "macos-version": "^6.0.0", + "subsume": "^4.0.0", + "type-fest": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-jxa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-jxa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-jxa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/run-jxa/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-jxa/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.8.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", + "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", + "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==", + "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==", + "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/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/subsume": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/subsume/-/subsume-4.0.0.tgz", + "integrity": "sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0", + "unique-string": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/subsume/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.2.tgz", + "integrity": "sha512-c/+AKMW1KB81r28Sp0AAdcyXj5dzNmAsZFcsrxKbZTKmcBK2xIwBR31dcSYSDu3bKmzXe8GKrSxO+eDQ401Uzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.72.2", + "@typescript-eslint/parser": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/utils": "8.72.2" + }, + "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": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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/wsl-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.4.0.tgz", + "integrity": "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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/contrib/raycast/package.json b/contrib/raycast/package.json new file mode 100644 index 00000000..08982c46 --- /dev/null +++ b/contrib/raycast/package.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "defi", + "title": "Defi", + "description": "Switch windows, jump between workspaces, and run Defi window manager commands", + "icon": "icon.png", + "author": "qeude", + "license": "MIT", + "platforms": [ + "macOS" + ], + "categories": [ + "System", + "Productivity" + ], + "commands": [ + { + "name": "search-windows", + "title": "Search Windows", + "description": "Find any window Defi manages, then focus it, bring it here, or move it", + "mode": "view", + "keywords": [ + "switch", + "window", + "focus", + "alt tab", + "app" + ] + }, + { + "name": "search-workspaces", + "title": "Search Workspaces", + "description": "Jump to a workspace on any display, or move the focused window there", + "mode": "view", + "keywords": [ + "space", + "desktop", + "virtual", + "display", + "monitor" + ] + }, + { + "name": "run-command", + "title": "Search Commands", + "description": "Run any Defi command; type 60 for a width or a workspace name to go there", + "mode": "view", + "keywords": [ + "tiling", + "column", + "width", + "layout", + "overview", + "stack" + ], + "arguments": [ + { + "name": "query", + "type": "text", + "placeholder": "Width or workspace", + "required": false + } + ] + } + ], + "preferences": [ + { + "name": "defiApp", + "title": "Defi App", + "description": "Leave empty to use Defi.app from /Applications or ~/Applications.", + "type": "appPicker", + "required": false + } + ], + "dependencies": { + "@raycast/api": "^2.11.1", + "@raycast/utils": "^2.3.3" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.2.0", + "@types/node": "26.6.5", + "@types/react": "19.3.0", + "eslint": "^10.0.1", + "prettier": "^3.8.1", + "typescript": "^5.9.3" + }, + "scripts": { + "build": "ray build -e dist -o dist", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "test": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --disable-warning=ExperimentalWarning --test 'test/*.test.mts'", + "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/contrib/raycast/src/commands.ts b/contrib/raycast/src/commands.ts new file mode 100644 index 00000000..7eac9ed3 --- /dev/null +++ b/contrib/raycast/src/commands.ts @@ -0,0 +1,325 @@ +import type { Icon } from "@raycast/api"; + +type IconName = keyof typeof Icon; + +export type Entry = { command: string; title: string; icon: IconName; shortcut?: string; hud?: string }; + +// Commands that take a value, offered as rows that put an example in the search bar. +export const prompts: { + section: string; + title: string; + icon: IconName; + example: string; + // The command in [keys] syntax, with a placeholder for the value. + syntax: string; + examples: string; + hint: string; + keywords: string[]; +}[] = [ + { + section: "Workspaces", + title: "Go to Workspace…", + icon: "ArrowRight", + example: "1", + syntax: "workspace ", + examples: "2, web", + hint: "Type a workspace's number or the start of its name, such as 2 or web.", + keywords: ["workspace", "go", "switch", "number", "name"], + }, + { + section: "Widths and Stacks", + title: "Set Column Width…", + icon: "ArrowsExpand", + example: "60", + syntax: "set-column-width ", + examples: "60, +10, 960", + hint: "Type a width: 60 for 60%, +10 or -10 to change it, or 960 for points.", + keywords: ["width", "resize", "size", "column"], + }, + { + section: "Widths and Stacks", + title: "Set Window Height…", + icon: "ChevronUpDown", + example: "h50", + syntax: "set-window-height ", + examples: "h50, h+10", + hint: "Type h and a height for the focused stacked window: h50, h+10, or h-10.", + keywords: ["height", "stack", "resize", "size"], + }, +]; + +const place: Record = { left: "on the left", right: "on the right", up: "above", down: "below" }; +const end: Record = { first: "start", last: "end" }; + +// What one row does, worded for its own argument; from CONFIGURATION.md "Commands". +export function describeCommand(command: string): string { + const [verb, arg = ""] = command.split(" "); + const where = place[arg]; + const display = `the display ${where}`; + const workspace = `the workspace ${where}`; + switch (verb) { + case "focus-column": + return where ? `Focus the column ${where}.` : `Focus the ${arg} column of the strip.`; + case "focus-window": + return where + ? `Focus the window ${where} in the stack.` + : `Focus the ${arg === "first" ? "top" : "bottom"} window of the stack.`; + case "focus-window-or-workspace": + return `Focus the window ${where}, or past the ${arg === "up" ? "top" : "bottom"} of the stack, ${workspace}.`; + case "focus-column-or-monitor": + return `Focus the column ${where}, or past the ${arg === "left" ? "first" : "last"} column, ${display}.`; + case "focus-floating": + return `Focus the ${arg} floating window${arg === "previous" || arg === "next" ? ", wrapping around" : ""}.`; + case "focus-monitor": + return `Focus ${display}.`; + case "focus-workspace": + return `Go to ${workspace} on this display, without wrapping.`; + case "reorder-workspace": + return `Move the active workspace ${arg} its display's stack.`; + case "move-workspace-to-monitor": + return `Move the active workspace to ${display} and remember it there.`; + case "move-column": + return where + ? `Move the focused column one place ${where}.` + : `Move the focused column to the ${end[arg]} of the strip.`; + case "move-window": + return `Move the focused window ${arg} its stack.`; + case "move-column-to-workspace": + return `Move the focused column to ${workspace} and follow it.`; + case "move-window-to-workspace": + return `Move only the focused window to ${workspace} and follow it.`; + case "send-column-to-workspace": + return `Move the focused column to ${workspace} without following it.`; + case "send-window-to-workspace": + return `Move only the focused window to ${workspace} without following it.`; + case "move-column-to-monitor": + return `Move the focused column to ${display}.`; + case "move-column-or-to-monitor": + return `Move the focused column ${arg}, or past the ${arg === "left" ? "first" : "last"} column, to ${display}.`; + case "move-window-to-monitor": + return `Move only the focused window to ${display}.`; + case "cycle-width": + return `Step to the ${arg} preset column width, wrapping around.`; + case "join-window": + return `Add the focused window to the stack ${where}.`; + case "consume-or-expel-window": + return `Alone in its column, the window joins the stack ${where}; in a stack, it leaves as a new column ${where}.`; + case "swap-window": + return `Swap the focused window with the one at the same row of the column ${where}.`; + default: + return descriptions[verb] ?? ""; + } +} + +// Commands that take no argument, or whose argument changes nothing in the wording. +const descriptions: Record = { + "focus-window-previous": "Go back to the window focused before this one, on any workspace or display.", + "activate-floating": "Select the floating layer and bring its selected window forward.", + "focus-workspace-previous": "Go back to the workspace this display showed before.", + "maximize-column": "Toggle the focused column between full width and its previous width.", + "expand-column-to-available-width": + "Grow the focused column into the free space beside the other fully visible columns.", + "center-column": "Center the focused column until you select another one.", + "center-visible-columns": "Center the fully visible columns as one group.", + "switch-preset-window-height": "Give the focused stacked window the next preset height, wrapping around.", + "reset-window-height": "Split the focused stack evenly again.", + "unjoin-windows": "Split the focused window out of its stack into a new column.", + "toggle-floating": "Switch the focused window between tiled and floating.", + "toggle-overview": "Open or close the Overview of every workspace.", + "toggle-cheatsheet": "Open or close the shortcut help.", + "open-settings": "Open Defi's Settings, even with the menu bar item hidden.", + "diagnostic-mark": "Record the current status and recent trace for a bug report, without changing any window.", +}; + +// Argument-free forms of the commands; shortcuts are Defi's built-in defaults. +export const sections: [string, Entry[]][] = [ + [ + "Focus", + [ + { command: "focus-column left", title: "Focus Left Column", icon: "ArrowLeft", shortcut: "mod-left" }, + { command: "focus-column right", title: "Focus Right Column", icon: "ArrowRight", shortcut: "mod-right" }, + { command: "focus-column first", title: "Focus First Column", icon: "ArrowLeftCircle", shortcut: "mod-home" }, + { command: "focus-column last", title: "Focus Last Column", icon: "ArrowRightCircle", shortcut: "mod-end" }, + { command: "focus-window up", title: "Focus Window Above", icon: "ArrowUp", shortcut: "mod-up" }, + { command: "focus-window down", title: "Focus Window Below", icon: "ArrowDown", shortcut: "mod-down" }, + { command: "focus-window first", title: "Focus Top Window", icon: "ArrowUpCircle" }, + { command: "focus-window last", title: "Focus Bottom Window", icon: "ArrowDownCircle" }, + { command: "focus-window-previous", title: "Focus Previous Window", icon: "Undo" }, + { command: "focus-window-or-workspace up", title: "Focus Window or Workspace Above", icon: "ArrowUp" }, + { command: "focus-window-or-workspace down", title: "Focus Window or Workspace Below", icon: "ArrowDown" }, + { command: "focus-column-or-monitor left", title: "Focus Left Column or Left Display", icon: "ArrowLeft" }, + { command: "focus-column-or-monitor right", title: "Focus Right Column or Right Display", icon: "ArrowRight" }, + { + command: "focus-floating previous", + title: "Focus Previous Floating Window", + icon: "ChevronLeft", + shortcut: "mod-shift-comma", + }, + { + command: "focus-floating next", + title: "Focus Next Floating Window", + icon: "ChevronRight", + shortcut: "mod-shift-period", + }, + { command: "focus-floating first", title: "Focus First Floating Window", icon: "ArrowLeftCircle" }, + { command: "focus-floating last", title: "Focus Last Floating Window", icon: "ArrowRightCircle" }, + { + command: "activate-floating", + title: "Bring Floating Windows Forward", + icon: "Layers", + shortcut: "mod-shift-v", + }, + ...(["left", "right", "up", "down"] as const).map((direction): Entry => ({ + command: `focus-monitor ${direction}`, + title: `Focus ${displayName(direction)}`, + icon: "Monitor", + shortcut: `mod-shift-${direction}`, + })), + ], + ], + [ + "Workspaces", + [ + { command: "focus-workspace up", title: "Go to Workspace Above", icon: "ChevronUp", shortcut: "mod-i" }, + { command: "focus-workspace down", title: "Go to Workspace Below", icon: "ChevronDown", shortcut: "mod-u" }, + { command: "focus-workspace-previous", title: "Go to Previous Workspace", icon: "Undo" }, + { command: "reorder-workspace up", title: "Move Workspace Up", icon: "ArrowUp", shortcut: "mod-shift-i" }, + { + command: "reorder-workspace down", + title: "Move Workspace Down", + icon: "ArrowDown", + shortcut: "mod-shift-u", + }, + ...(["left", "right", "up", "down"] as const).map((direction): Entry => ({ + command: `move-workspace-to-monitor ${direction}`, + title: `Move Workspace to ${displayName(direction)}`, + icon: "Monitor", + })), + ], + ], + [ + "Move", + [ + { command: "move-column left", title: "Move Column Left", icon: "ArrowLeft", shortcut: "mod-cmd-left" }, + { command: "move-column right", title: "Move Column Right", icon: "ArrowRight", shortcut: "mod-cmd-right" }, + { + command: "move-column first", + title: "Move Column to Start", + icon: "ArrowLeftCircle", + shortcut: "mod-cmd-home", + }, + { + command: "move-column last", + title: "Move Column to End", + icon: "ArrowRightCircle", + shortcut: "mod-cmd-end", + }, + { command: "move-window up", title: "Move Window Up", icon: "ArrowUp", shortcut: "mod-cmd-up" }, + { command: "move-window down", title: "Move Window Down", icon: "ArrowDown", shortcut: "mod-cmd-down" }, + { + command: "move-column-to-workspace up", + title: "Move Column to Workspace Above", + icon: "ChevronUp", + shortcut: "mod-cmd-i", + }, + { + command: "move-column-to-workspace down", + title: "Move Column to Workspace Below", + icon: "ChevronDown", + shortcut: "mod-cmd-u", + }, + { command: "move-window-to-workspace up", title: "Move Window to Workspace Above", icon: "ChevronUp" }, + { command: "move-window-to-workspace down", title: "Move Window to Workspace Below", icon: "ChevronDown" }, + { command: "send-column-to-workspace up", title: "Send Column to Workspace Above", icon: "ArrowNe" }, + { command: "send-column-to-workspace down", title: "Send Column to Workspace Below", icon: "ArrowNe" }, + { command: "send-window-to-workspace up", title: "Send Window to Workspace Above", icon: "ArrowNe" }, + { command: "send-window-to-workspace down", title: "Send Window to Workspace Below", icon: "ArrowNe" }, + ...(["left", "right", "up", "down"] as const).map((direction): Entry => ({ + command: `move-column-to-monitor ${direction}`, + title: `Move Column to ${displayName(direction)}`, + icon: "Monitor", + shortcut: `mod-shift-cmd-${direction}`, + })), + { command: "move-column-or-to-monitor left", title: "Move Column Left or to Left Display", icon: "ArrowLeft" }, + { + command: "move-column-or-to-monitor right", + title: "Move Column Right or to Right Display", + icon: "ArrowRight", + }, + ...(["left", "right", "up", "down"] as const).map((direction): Entry => ({ + command: `move-window-to-monitor ${direction}`, + title: `Move Window to ${displayName(direction)}`, + icon: "Monitor", + })), + ], + ], + [ + "Widths and Stacks", + [ + { command: "cycle-width next", title: "Cycle to Next Column Width", icon: "ArrowsExpand", shortcut: "mod-equal" }, + { + command: "cycle-width previous", + title: "Cycle to Previous Column Width", + icon: "ArrowsContract", + shortcut: "mod-minus", + }, + { command: "maximize-column", title: "Maximize Column", icon: "Maximize", shortcut: "mod-f" }, + { command: "expand-column-to-available-width", title: "Fill Available Width", icon: "ArrowsExpand" }, + { command: "center-column", title: "Center Column", icon: "Center", shortcut: "mod-c" }, + { command: "center-visible-columns", title: "Center Visible Columns", icon: "AlignCentre" }, + { command: "switch-preset-window-height", title: "Cycle to Next Window Height", icon: "ChevronUpDown" }, + { command: "reset-window-height", title: "Reset Window Heights", icon: "RotateAntiClockwise" }, + { + command: "join-window left", + title: "Stack into Left Column", + icon: "AppWindowList", + shortcut: "mod-semicolon", + }, + { + command: "join-window right", + title: "Stack into Right Column", + icon: "AppWindowList", + shortcut: "mod-quote", + }, + { command: "unjoin-windows", title: "Unstack Window", icon: "AppWindowGrid2x2", shortcut: "mod-period" }, + { + command: "consume-or-expel-window left", + title: "Stack or Unstack Left", + icon: "AppWindowSidebarLeft", + shortcut: "mod-leftbracket", + }, + { + command: "consume-or-expel-window right", + title: "Stack or Unstack Right", + icon: "AppWindowSidebarRight", + shortcut: "mod-rightbracket", + }, + { command: "swap-window left", title: "Swap with Left Window", icon: "Switch" }, + { command: "swap-window right", title: "Swap with Right Window", icon: "Switch" }, + { command: "toggle-floating", title: "Float or Tile Window", icon: "Window", shortcut: "mod-v" }, + ], + ], + [ + "Other", + [ + { command: "toggle-overview", title: "Toggle Overview", icon: "AppWindowGrid3x3", shortcut: "mod-o" }, + { command: "toggle-cheatsheet", title: "Toggle Cheatsheet", icon: "Keyboard", shortcut: "mod-shift-slash" }, + { command: "open-settings", title: "Open Settings", icon: "Gear" }, + { + command: "diagnostic-mark", + title: "Mark a Problem for Diagnostics", + icon: "Bug", + hud: "Marked for diagnostics", + }, + ], + ], +]; + +function cap(word: string) { + return word[0].toUpperCase() + word.slice(1); +} + +function displayName(direction: "left" | "right" | "up" | "down") { + if (direction === "left" || direction === "right") return `${cap(direction)} Display`; + return `Display ${direction === "up" ? "Above" : "Below"}`; +} diff --git a/contrib/raycast/src/defi.ts b/contrib/raycast/src/defi.ts new file mode 100644 index 00000000..a86dbe3c --- /dev/null +++ b/contrib/raycast/src/defi.ts @@ -0,0 +1,64 @@ +import { closeMainWindow, getPreferenceValues, PopToRootType, showHUD } from "@raycast/api"; +import { execFile } from "node:child_process"; +import { accessSync, constants } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; + +export const notRunning = "Defi is not running."; +export const notInstalled = "Defi is not installed."; +const cliInApp = "Contents/MacOS/defi"; + +function isExecutable(path: string): boolean { + try { + accessSync(path, constants.X_OK); + return true; + } catch { + return false; + } +} + +// Raycast stores a picked app as an object, Tinycast as its path. +function pickedApp(): string | undefined { + const picked = getPreferenceValues().defiApp as unknown; + const path = typeof picked === "string" ? picked : (picked as { path?: string } | undefined)?.path; + return path?.trim() || undefined; +} + +export function defiAppPath(): string { + const picked = pickedApp(); + if (picked) { + if (isExecutable(join(picked, cliInApp))) return picked; + throw new Error(`${picked} has no defi command line tool. Pick Defi.app in the extension preferences.`); + } + const found = ["/Applications/Defi.app", join(homedir(), "Applications/Defi.app")].find((app) => + isExecutable(join(app, cliInApp)), + ); + if (!found) throw new Error(notInstalled); + return found; +} + +export function defi(...args: string[]): Promise { + const cli = join(defiAppPath(), cliInApp); + return new Promise((resolve, reject) => { + execFile(cli, args, { timeout: 5000 }, (error, stdout, stderr) => { + if (!error) return resolve(stdout); + if (error.killed) return reject(new Error("Defi did not answer in time.")); + const message = stderr.trim() || error.message; + reject(new Error(message.includes("connect failed") ? notRunning : message)); + }); + }); +} + +// Close Raycast first so the command acts on the window that was focused before it. +// Feedback is a HUD because the launcher window is already gone. +export async function runAndClose(commands: string[][], hud?: string): Promise { + await closeMainWindow({ clearRootSearch: true, popToRootType: PopToRootType.Immediate }); + try { + for (const args of commands) await defi(...args); + if (hud) await showHUD(hud); + } catch (error) { + // A HUD holds one line, so keep the first sentence of Defi's reason. + const message = error instanceof Error ? error.message : String(error); + await showHUD(message.split(/(?<=[.!?])\s/)[0]); + } +} diff --git a/contrib/raycast/src/desktop.ts b/contrib/raycast/src/desktop.ts new file mode 100644 index 00000000..3b6d64d0 --- /dev/null +++ b/contrib/raycast/src/desktop.ts @@ -0,0 +1,45 @@ +import { getApplications } from "@raycast/api"; +import { useCachedPromise } from "@raycast/utils"; +import { defi } from "./defi"; +import { parseWindows, parseWorkspaces } from "./parse"; + +export async function readDesktop() { + const [windows, workspaces] = await Promise.all([ + defi("list-windows", "--json").then(parseWindows), + defi("list-workspaces", "--json").then(parseWorkspaces), + ]); + return { windows, workspaces }; +} + +async function readApps() { + return (await getApplications()).map(({ bundleId, name, path }) => ({ bundleId, name, path })); +} + +// Windows and workspaces, shown from the last run at once while they reload. +// No live stream: launchers do not always run effect cleanups, which would leave `defi subscribe` running. +export function useDesktop() { + // The empty view reports the error with its recovery actions, so no toast as well. + const desktop = useCachedPromise(readDesktop, [], { onError: () => undefined }); + const apps = useCachedPromise(readApps); + const appsByID = new Map(apps.data?.map((app) => [app.bundleId, app])); + const windows = desktop.data?.windows; + return { + windows, + workspaces: desktop.data?.workspaces, + isLoading: desktop.isLoading, + error: desktop.error, + revalidate: desktop.revalidate, + // The state to act on: a fresh read while the cached one is still reloading. + fresh: async () => (desktop.isLoading || !desktop.data ? await readDesktop() : desktop.data), + displayCount: new Set(desktop.data?.workspaces.map((workspace) => workspace.display)).size, + focused: windows?.find((window) => window.focused), + // The workspace the user is on, even when it holds no window. + current: desktop.data?.workspaces.find((workspace) => workspace.active && workspace.focusedDisplay), + appIcon: (bundleID?: string) => { + const path = bundleID ? appsByID.get(bundleID)?.path : undefined; + return path ? { fileIcon: path } : undefined; + }, + appName: (bundleID: string) => + appsByID.get(bundleID)?.name ?? windows?.find((window) => window.appID === bundleID)?.appName ?? bundleID, + }; +} diff --git a/contrib/raycast/src/error-view.tsx b/contrib/raycast/src/error-view.tsx new file mode 100644 index 00000000..82ec7d06 --- /dev/null +++ b/contrib/raycast/src/error-view.tsx @@ -0,0 +1,90 @@ +import { Action, ActionPanel, Icon, List, open, openExtensionPreferences, showToast, Toast } from "@raycast/api"; +import { defi, defiAppPath, notInstalled, notRunning } from "./defi"; +import { VersionMismatch } from "./parse"; + +const releases = "https://github.com/qeude/Defi/releases/latest"; + +export function describeError(error: Error) { + if (error.message === notRunning) { + return { icon: Icon.Power, title: "Defi Is Not Running", description: "Press Enter to launch it." }; + } + if (error.message === notInstalled) { + return { + icon: Icon.Download, + title: "Defi Is Not Installed", + description: "Download Defi, or pick Defi.app in the extension preferences.", + }; + } + if (error instanceof VersionMismatch) { + return { + icon: Icon.ArrowClockwise, + title: error.received > error.supported ? "Update This Extension" : "Update Defi", + description: + error.received > error.supported + ? "Defi is newer than this extension. Update the extension from the Raycast Store." + : "This extension needs a newer Defi. Update Defi from its menu bar item or the releases page.", + }; + } + return { icon: Icon.Warning, title: "Cannot Reach Defi", description: error.message }; +} + +const launchAttempts = 10; + +// Opens Defi and keeps the launcher open, retrying until Defi answers. +async function launch(onRetry: () => void) { + const toast = await showToast({ style: Toast.Style.Animated, title: "Launching Defi…" }); + try { + await open(defiAppPath()); + } catch (error) { + toast.style = Toast.Style.Failure; + toast.title = "Cannot Launch Defi"; + toast.message = error instanceof Error ? error.message : String(error); + return; + } + for (let attempt = 0; attempt < launchAttempts; attempt += 1) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + try { + await defi("list-workspaces", "--json"); + onRetry(); + toast.style = Toast.Style.Success; + toast.title = "Defi Is Running"; + return; + } catch { + // Not answering yet. + } + } + toast.style = Toast.Style.Failure; + toast.title = "Defi Did Not Start"; + toast.primaryAction = { title: "Try Again", onAction: () => launch(onRetry) }; +} + +// Recovery for every read failure: install or launch Defi, try again, or pick the app in the preferences. +export function RecoveryActions({ error, onRetry }: { error: Error; onRetry: () => void }) { + const preferences = ( + + ); + if (error.message === notInstalled) { + return ( + + + {preferences} + + ); + } + return ( + + {error.message === notRunning ? ( + launch(onRetry)} /> + ) : null} + {error instanceof VersionMismatch && error.received < error.supported ? ( + + ) : null} + + {preferences} + + ); +} + +export function ErrorView({ error, onRetry }: { error: Error; onRetry: () => void }) { + return } />; +} diff --git a/contrib/raycast/src/parse.ts b/contrib/raycast/src/parse.ts new file mode 100644 index 00000000..cab0fd42 --- /dev/null +++ b/contrib/raycast/src/parse.ts @@ -0,0 +1,96 @@ +export type DefiWindow = { + id: number; + appID?: string; + appName: string; + title: string; + workspace?: { id: string; name?: string; position: number }; + display?: number; + // 1-based place in the strip; absent for floating windows. + column?: number; + row?: number; + floating: boolean; + focused: boolean; +}; + +export type DefiWorkspace = { + id: string; + name?: string; + // SF Symbol name set in [workspaces] icons. + icon?: string; + position: number; + kind: string; + display: number; + active: boolean; + windowCount: number; + applications: string[]; + // On the display that holds the focus; position suggestions resolve there. + focusedDisplay: boolean; +}; + +export class VersionMismatch extends Error { + received: number; + supported: number; + constructor(received: number, supported: number) { + super(`Defi sent format version ${received}; this extension reads version ${supported}.`); + this.received = received; + this.supported = supported; + } +} + +// Fields within a version may grow; a new version may break, so it is refused. +// Output that is not versioned JSON comes from a Defi older than the format, so it reads as version 0. +function versioned(text: string, version: number): Record { + let json; + try { + json = JSON.parse(text); + } catch { + throw new VersionMismatch(0, version); + } + if (json?.version !== version) throw new VersionMismatch(Number(json?.version) || 0, version); + return json; +} + +export function parseWindows(text: string): DefiWindow[] { + const { windows } = versioned(text, 1); + if (!Array.isArray(windows)) return []; + return windows + .filter((window) => typeof window?.id === "number") + .map((window) => ({ + id: window.id, + appID: window.appID, + appName: String(window.appName ?? ""), + title: String(window.title ?? ""), + workspace: window.workspace, + display: window.display, + column: window.column, + row: window.row, + floating: window.floating === true, + focused: window.focused === true, + })); +} + +export function parseWorkspaces(text: string): DefiWorkspace[] { + const { monitors } = versioned(text, 2); + if (!Array.isArray(monitors)) return []; + return monitors.flatMap((monitor) => + (Array.isArray(monitor?.workspaces) ? monitor.workspaces : []) + .filter((workspace: { id?: unknown }) => typeof workspace?.id === "string") + .map((workspace: Record) => ({ + id: workspace.id, + name: workspace.name, + icon: typeof workspace.icon === "string" ? workspace.icon : undefined, + position: workspace.position, + kind: String(workspace.kind ?? ""), + display: monitor.display, + active: workspace.active === true, + windowCount: Number(workspace.windowCount ?? 0), + applications: Array.isArray(workspace.applications) ? workspace.applications.map(String) : [], + focusedDisplay: monitor.focused === true, + })), + ); +} + +export function workspaceLabel(workspace: { name?: string; position: number; kind?: string }): string { + if (workspace.kind === "trailing") return "New Workspace"; + return workspace.name ?? `Workspace ${workspace.position}`; +} diff --git a/contrib/raycast/src/run-command.tsx b/contrib/raycast/src/run-command.tsx new file mode 100644 index 00000000..76098af4 --- /dev/null +++ b/contrib/raycast/src/run-command.tsx @@ -0,0 +1,232 @@ +import { Action, ActionPanel, Icon, Keyboard, LaunchProps, List } from "@raycast/api"; +import { useCachedState } from "@raycast/utils"; +import { useState } from "react"; +import { describeCommand, prompts, sections } from "./commands"; +import { runAndClose } from "./defi"; +import { useDesktop } from "./desktop"; +import { describeError, RecoveryActions } from "./error-view"; +import { formatShortcut } from "./shortcut"; +import { forWorkspace, suggestions, type Suggestion } from "./suggest"; + +const entries = sections.flatMap(([section, list]) => list.map((entry) => ({ ...entry, section }))); +const recentLimit = 5; + +// A workspace suggestion is kept as its workspace and action, and worded again from the live state, +// so a reordered or moved workspace never shows a stale label or display. +type Recent = { command: string } | { suggestion: Suggestion } | { workspaceID: string; action: string }; + +const recentKey = (run: Recent) => + "command" in run + ? run.command + : "suggestion" in run + ? run.suggestion.args.join(" ") + : `${run.action} ${run.workspaceID}`; + +const suggestionIcons: Record = { + width: Icon.ArrowsExpand, + height: Icon.ChevronUpDown, + workspace: Icon.ArrowRight, + column: Icon.AppWindowSidebarLeft, + window: Icon.Window, +}; + +// What a row does, then the line to paste into [keys]; Default Shortcut appears where Defi binds +// one, or as None for a command that has none. +function CommandDetail({ text, command, shortcut }: { text: string; command?: string; shortcut?: string | null }) { + // Without a default shortcut, the key in the line is a stand-in, and says so. + const key = shortcut ? `"${shortcut}"` : '"mod-x"'; + const note = shortcut ? "" : " # pick your own key"; + const binding = command ? `\n\n\`\`\`toml\n${key} = "${command}"${note}\n\`\`\`` : ""; + return ( + + + + ) : undefined + } + /> + ); +} + +export default function Command(props: LaunchProps<{ arguments: Arguments.RunCommand }>) { + // An empty optional argument arrives as "", so fall through to the root-search text. + const [searchText, setSearchText] = useState(props.arguments?.query || props.fallbackText || ""); + const [recent, setRecent] = useCachedState("recent-runs", []); + const [showDetail, setShowDetail] = useCachedState("show-command-detail", true); + const desktop = useDesktop(); + const problem = desktop.error; + const suggested = suggestions(searchText, problem ? [] : (desktop.workspaces ?? [])); + const live = desktop.workspaces ?? []; + const recentRows = searchText + ? [] + : recent.flatMap((run): (Suggestion | string)[] => { + if ("command" in run) return [run.command]; + if ("suggestion" in run) return [run.suggestion]; + // A remembered move to a workspace that has since gone is dropped rather than offered. + const workspace = live.find((candidate) => candidate.id === run.workspaceID); + const suggestion = workspace && forWorkspace(workspace, live).find((s) => s.args[0] === run.action); + return suggestion ? [suggestion] : []; + }); + + const remember = (run: Recent) => { + setRecent([run, ...recent.filter((other) => recentKey(other) !== recentKey(run))].slice(0, recentLimit)); + }; + + const run = (entry: (typeof entries)[number]) => { + remember({ command: entry.command }); + return runAndClose([entry.command.split(" ")], entry.hud); + }; + + // Runs a suggestion, naming the window it moves from a fresh read. + const runSuggestion = async (suggestion: Suggestion) => { + const workspaceID = + suggestion.kind === "width" || suggestion.kind === "height" ? undefined : suggestion.args.at(-1); + remember(workspaceID ? { workspaceID, action: suggestion.args[0] } : { suggestion }); + const app = suggestion.hud?.includes("{window}") + ? (await desktop.fresh().catch(() => undefined))?.windows.find((window) => window.focused)?.appName + : undefined; + await runAndClose([suggestion.args], suggestion.hud?.replace("{window}", app ?? "window")); + }; + + const suggestionItem = (suggestion: Suggestion, keyPrefix = "") => ( + + } + actions={ + + runSuggestion(suggestion)} /> + {/* Copy keeps ⌘↵ on every row; without a [keys] form, it copies the CLI call. */} + + {toggleDetail} + + } + /> + ); + + const toggleDetail = ( + + setShowDetail(!showDetail)} + /> + + + ); + + const item = (entry: (typeof entries)[number], keyPrefix = "") => ( + + } + actions={ + + run(entry)} /> + + {toggleDetail} + + } + /> + ); + + return ( + 0)} + isShowingDetail={showDetail} + searchText={searchText} + onSearchTextChange={setSearchText} + searchBarPlaceholder="Search commands, or type 60, +10, 2…" + > + {problem ? ( + + } + actions={} + /> + + ) : null} + {suggested.length > 0 ? ( + {suggested.map((suggestion) => suggestionItem(suggestion))} + ) : null} + {recentRows.length > 0 ? ( + + {recentRows.map((run) => { + if (typeof run !== "string") return suggestionItem(run, "recent-"); + const entry = entries.find((candidate) => candidate.command === run); + return entry ? item(entry, "recent-") : null; + })} + + ) : null} + {sections.map(([section]) => ( + + {/* Example rows stay findable by search, and step aside once the search is itself an argument. */} + {suggested.length > 0 + ? null + : prompts + .filter((prompt) => prompt.section === section) + .map((prompt) => ( + } + actions={ + + setSearchText(prompt.example)} + /> + + {toggleDetail} + + } + /> + ))} + {entries.filter((entry) => entry.section === section).map((entry) => item(entry))} + + ))} + + ); +} diff --git a/contrib/raycast/src/search-windows.tsx b/contrib/raycast/src/search-windows.tsx new file mode 100644 index 00000000..f85bf8be --- /dev/null +++ b/contrib/raycast/src/search-windows.tsx @@ -0,0 +1,6 @@ +import { LaunchProps } from "@raycast/api"; +import { WindowList } from "./window-list"; + +export default function Command(props: LaunchProps) { + return ; +} diff --git a/contrib/raycast/src/search-workspaces.tsx b/contrib/raycast/src/search-workspaces.tsx new file mode 100644 index 00000000..b2d207bc --- /dev/null +++ b/contrib/raycast/src/search-workspaces.tsx @@ -0,0 +1,189 @@ +import { + Action, + ActionPanel, + Color, + environment, + Icon, + Keyboard, + LaunchProps, + List, + showToast, + Toast, +} from "@raycast/api"; +import { showFailureToast, useCachedState } from "@raycast/utils"; +import { useState } from "react"; +import { runAndClose } from "./defi"; +import { useDesktop } from "./desktop"; +import { ErrorView } from "./error-view"; +import { workspaceLabel, type DefiWorkspace } from "./parse"; +import { StripDetail } from "./strip-detail"; +import { WindowList } from "./window-list"; +import { workspaceAccessories, workspaceIcon } from "./workspace-icon"; + +export default function Command(props: LaunchProps) { + const desktop = useDesktop(); + const { windows, workspaces, focused, displayCount, appIcon, appName } = desktop; + const [searchText, setSearchText] = useState(props.fallbackText ?? ""); + const [showDetail, setShowDetail] = useCachedState("workspaces-detail", false); + const dark = environment.appearance === "dark"; + // Cached rows would look live while Defi is unreachable, so an error hides them. + const shown = desktop.error ? [] : (workspaces ?? []); + const displays = [...new Set(shown.map((workspace) => workspace.display))]; + + const actions = (workspace: DefiWorkspace, label: string) => { + // The cached focus may be stale while the list reloads, so moves check a fresh read. + const move = (command: string, describe: (app: string) => string) => async () => { + let state; + try { + state = await desktop.fresh(); + } catch (error) { + await showFailureToast(error, { title: "Cannot Reach Defi" }); + return; + } + const latest = state.windows.find((window) => window.focused); + if (!latest || latest.workspace?.id === workspace.id) { + // Already there is not a failure; no focused window is. + await showToast( + latest + ? { style: Toast.Style.Success, title: `Already on ${label}` } + : { style: Toast.Style.Failure, title: "No Focused Window" }, + ); + return; + } + await runAndClose([[command, workspace.id]], describe(latest.appName)); + }; + return ( + + + runAndClose([["--monitor", String(workspace.display), "workspace", workspace.id]])} + /> + {/* The secondary action keeps ⌘↵ on every row, so it stays even where it has nothing to move. */} + `Moved ${app} to ${label}`)} + /> + `Sent ${app} to ${label}`)} + /> + + + `Moved column to ${label}`)} + /> + `Sent column to ${label}`)} + /> + + + {workspace.windowCount > 0 ? ( + } + /> + ) : null} + setShowDetail(!showDetail)} + /> + + + + ); + }; + + return ( + 0} + searchBarPlaceholder="Search workspaces by name, number, or app" + searchText={searchText} + onSearchTextChange={setSearchText} + filtering + > + {desktop.error ? ( + + ) : desktop.isLoading ? null : ( + + )} + {displays.map((display) => { + const onDisplay = shown.filter((workspace) => workspace.display === display); + return ( + 1 ? `Display ${display}` : undefined} + subtitle={displayCount > 1 && onDisplay[0]?.focusedDisplay ? "Focused" : undefined} + > + {onDisplay.map((workspace) => { + const label = workspaceLabel(workspace); + return ( + + + {workspace.active ? ( + + + + ) : null} + {displayCount > 1 ? ( + + ) : null} + + } + actions={actions(workspace, label)} + /> + ); + })} + + ); + })} + + ); +} diff --git a/contrib/raycast/src/shortcut.ts b/contrib/raycast/src/shortcut.ts new file mode 100644 index 00000000..aaea2463 --- /dev/null +++ b/contrib/raycast/src/shortcut.ts @@ -0,0 +1,30 @@ +const modifiers: Record = { ctrl: "⌃", alt: "⌥", shift: "⇧", cmd: "⌘" }; +const keys: Record = { + left: "←", + right: "→", + up: "↑", + down: "↓", + home: "↖", + end: "↘", + pageup: "⇞", + pagedown: "⇟", + comma: ",", + period: ".", + slash: "/", + semicolon: ";", + quote: "'", + leftbracket: "[", + rightbracket: "]", + minus: "-", + equal: "=", + tab: "⇥", +}; + +// "mod-shift-comma" as macOS glyphs with Defi's default mod (ctrl+alt): "⌃⌥⇧,". +export function formatShortcut(shortcut: string): string { + const parts = shortcut.split("-"); + const key = parts.pop() ?? ""; + const held = new Set(parts.flatMap((part) => (part === "mod" ? ["ctrl", "alt"] : [part]))); + const glyphs = ["ctrl", "alt", "shift", "cmd"].filter((name) => held.has(name)).map((name) => modifiers[name]); + return glyphs.join("") + (keys[key] ?? key.toUpperCase()); +} diff --git a/contrib/raycast/src/strip-detail.tsx b/contrib/raycast/src/strip-detail.tsx new file mode 100644 index 00000000..45fa5c26 --- /dev/null +++ b/contrib/raycast/src/strip-detail.tsx @@ -0,0 +1,56 @@ +import { Color, Image, List } from "@raycast/api"; +import type { DefiWindow } from "./parse"; +import { stripColumns, stripImage } from "./strip"; + +type Props = { + windows: DefiWindow[]; + workspaceID: string; + highlightID?: number; + dark: boolean; + appIcon: (bundleID?: string) => Image.ImageLike | undefined; + empty: string; + children?: React.ReactNode; +}; + +const maxTagLength = 24; + +// The workspace's strip, then the caller's rows, then its columns named in order. +export function StripDetail({ windows, workspaceID, highlightID, dark, appIcon, empty, children }: Props) { + const columns = stripColumns(windows, workspaceID); + const floating = windows.filter((window) => window.workspace?.id === workspaceID && window.floating); + const onWorkspace = [...columns.flat(), ...floating]; + // When one app fills several columns, its name tells them apart no more; its titles do. + const repeated = (window: DefiWindow) => onWorkspace.filter((other) => other.appID === window.appID).length > 1; + const name = (window: DefiWindow) => { + if (!repeated(window) || !window.title) return window.appName; + return window.title.length > maxTagLength ? `${window.title.slice(0, maxTagLength - 1)}…` : window.title; + }; + const tags = (list: DefiWindow[]) => + list.map((window) => ( + + )); + return ( + + {children} + {onWorkspace.length > 0 ? : null} + {columns.map((column, index) => ( + + {tags(column)} + + ))} + {floating.length > 0 ? ( + {tags(floating)} + ) : null} + + } + /> + ); +} diff --git a/contrib/raycast/src/strip.ts b/contrib/raycast/src/strip.ts new file mode 100644 index 00000000..80ff1df0 --- /dev/null +++ b/contrib/raycast/src/strip.ts @@ -0,0 +1,49 @@ +import type { DefiWindow } from "./parse"; + +const column = { width: 44, height: 64, gap: 6, inset: 8 }; +const minWidth = 240; +const palette = { + // Six-digit colors with separate opacities: some native SVG renderers ignore eight-digit hex. + light: { desk: ["#000000", 0.06], window: ["#FFFFFF", 1], stroke: ["#000000", 0.12], highlight: "#007AFF" }, + dark: { desk: ["#FFFFFF", 0.07], window: ["#FFFFFF", 0.14], stroke: ["#FFFFFF", 0.12], highlight: "#0A84FF" }, +}; + +// Tiled windows of one workspace, as columns of stacked windows in strip order. +export function stripColumns(windows: DefiWindow[], workspaceID: string): DefiWindow[][] { + const columns: DefiWindow[][] = []; + for (const window of windows) { + if (window.workspace?.id !== workspaceID || !window.column) continue; + (columns[window.column - 1] ??= []).push(window); + } + return Array.from(columns, (windows) => (windows ?? []).sort((a, b) => (a.row ?? 0) - (b.row ?? 0))); +} + +// The strip as an SVG: one rounded tile per window, the highlighted one in the accent color. +export function stripSVG(columns: DefiWindow[][], highlightID: number | undefined, dark: boolean): string { + const colors = dark ? palette.dark : palette.light; + const stripWidth = Math.max(columns.length, 1) * (column.width + column.gap) - column.gap; + const width = Math.max(minWidth, column.inset * 2 + stripWidth); + const left = (width - stripWidth) / 2; + const tiles = columns.flatMap((windows, index) => { + const rowHeight = (column.height - column.gap * (windows.length - 1)) / Math.max(windows.length, 1); + return windows.map((window, row) => { + const highlighted = window.id === highlightID; + const x = left + index * (column.width + column.gap); + const y = column.inset + row * (rowHeight + column.gap); + const [fill, fillOpacity] = highlighted ? [colors.highlight, 1] : colors.window; + const [stroke, strokeOpacity] = highlighted ? [colors.highlight, 1] : colors.stroke; + return ``; + }); + }); + const height = column.inset * 2 + column.height; + const desk = ``; + return `${desk}${tiles.join("")}`; +} + +// Markdown image of a workspace's strip, or nothing when it has no tiled window. +export function stripImage(windows: DefiWindow[], workspaceID: string, highlightID: number | undefined, dark: boolean) { + const columns = stripColumns(windows, workspaceID); + if (columns.length === 0) return ""; + const svg = Buffer.from(stripSVG(columns, highlightID, dark)).toString("base64"); + return `![Strip](data:image/svg+xml;base64,${svg})`; +} diff --git a/contrib/raycast/src/suggest.ts b/contrib/raycast/src/suggest.ts new file mode 100644 index 00000000..807877c0 --- /dev/null +++ b/contrib/raycast/src/suggest.ts @@ -0,0 +1,158 @@ +import { workspaceLabel, type DefiWorkspace } from "./parse.ts"; + +export type Suggestion = { + title: string; + description: string; + // What runs; `binding` is the same command in the form [keys] accepts, when there is one. + args: string[]; + binding?: string; + // Defi's built-in shortcut for it, in [keys] syntax, when it has one. + shortcut?: string; + kind: "width" | "height" | "workspace" | "column" | "window"; + // `{window}` stands for the focused window's app, filled in when the command runs. + hud?: string; +}; + +// Go to, then the four moves in the order Search Workspaces offers them. +function workspaceSuggestions(workspace: DefiWorkspace, namedCount: number): Suggestion[] { + const label = workspaceLabel(workspace); + const named = workspace.name !== undefined; + const target = (byName: string, byPosition?: string) => + named ? `${byName} ${workspace.name}` : byPosition && `${byPosition} ${workspace.position}`; + // mod-1 … mod-9 go to the named workspaces in [workspaces].names order, which the CLI does not + // report, then to positions on the focused display; only that second case is known here. + const digit = + !named && workspace.focusedDisplay && workspace.position > namedCount && workspace.position <= 9 + ? String(workspace.position) + : undefined; + return [ + { + title: `Go to ${label}`, + description: `Shows ${label} on its display.`, + args: ["--monitor", String(workspace.display), "workspace", workspace.id], + binding: target("workspace", "focus-workspace-position"), + shortcut: digit && `mod-${digit}`, + kind: "workspace", + }, + { + title: `Move Focused Window to ${label}`, + description: `Moves the focused window to ${label} and follows it.`, + args: ["move-window-to-workspace-name", workspace.id], + binding: target("move-window-to-workspace-name", "move-window-to-workspace-position"), + kind: "window", + hud: `Moved {window} to ${label}`, + }, + { + title: `Send Focused Window to ${label}`, + description: `Moves the focused window to ${label} and stays here.`, + args: ["send-window-to-workspace-name", workspace.id], + binding: target("send-window-to-workspace-name", "send-window-to-workspace-position"), + kind: "window", + hud: `Sent {window} to ${label}`, + }, + { + title: `Move Focused Column to ${label}`, + description: `Moves the focused column to ${label} and follows it.`, + args: ["move-column-to-workspace-name", workspace.id], + binding: target("move-column-to-workspace-name", "move-column-to-workspace-position"), + shortcut: digit && `mod-cmd-${digit}`, + kind: "column", + hud: `Moved column to ${label}`, + }, + { + title: `Send Focused Column to ${label}`, + description: `Moves the focused column to ${label} and stays here.`, + args: ["send-column-to-workspace-name", workspace.id], + binding: target("send-column-to-workspace-name"), + kind: "column", + hud: `Sent column to ${label}`, + }, + ]; +} + +const verbs = { + width: { "+": ["Widen", "Widened", "Widens"], "-": ["Narrow", "Narrowed", "Narrows"] }, + height: { "+": ["Grow", "Grew", "Grows"], "-": ["Shrink", "Shrank", "Shrinks"] }, +} as const; + +function sized(kind: "width" | "height", sign: "+" | "-" | undefined, amount: number, points: boolean): Suggestion { + const value = `${sign ?? ""}${amount}${points ? "" : "%"}`; + const shown = points ? `${amount} pt` : `${amount}%`; + const width = kind === "width"; + const subject = width ? "column" : "window"; + const whole = width ? "the usable width" : "the column height"; + const command = width ? "set-column-width" : "set-window-height"; + const fields = { args: [command, value], binding: `${command} ${value}`, kind }; + if (!sign) { + const noun = width ? "Column Width" : "Window Height"; + return { + ...fields, + title: `Set ${noun} to ${shown}`, + description: points + ? `Makes the focused ${subject} ${amount} points wide.` + : `Sets the focused ${width ? "column" : "stacked window"} to ${amount}% of ${whole}.`, + hud: `${width ? "Column width" : "Window height"} ${shown}`, + }; + } + const [verb, past, third] = verbs[kind][sign]; + return { + ...fields, + title: `${verb} ${width ? "Column" : "Window"} by ${shown}`, + description: `${third} the focused ${width ? "column" : "stacked window"} by ${points ? `${amount} points` : `${amount}% of ${whole}`}.`, + hud: `${past} ${subject} by ${shown}`, + }; +} + +// A workspace matches when the search starts its name or one of its words: "sec" finds dev-secondary. +function matchesName(name: string, text: string): boolean { + const lowered = text.toLowerCase(); + const words = name.toLowerCase().split(/[-_.\s]+/); + return name.toLowerCase().startsWith(lowered) || words.some((word) => word.startsWith(lowered)); +} + +const minNameQuery = 2; + +// Commands whose argument is the search text: "60" sets a width, "h50" a stacked window's height, +// "+10" widens, "2" or "web" targets a workspace. +// Go to and the four moves for one workspace, worded from the live state. +export function forWorkspace(workspace: DefiWorkspace, workspaces: DefiWorkspace[]): Suggestion[] { + return workspaceSuggestions(workspace, workspaces.filter((other) => other.kind === "named").length); +} + +export function suggestions(query: string, workspaces: DefiWorkspace[]): Suggestion[] { + const text = query.trim(); + if (!text) return []; + const namedCount = workspaces.filter((workspace) => workspace.kind === "named").length; + const forWorkspace = (workspace: DefiWorkspace) => workspaceSuggestions(workspace, namedCount); + const result: Suggestion[] = []; + + const width = text.match(/^([+-])?(\d{1,4})\s*(%|pt|px)?$/i); + if (width) { + const [, sign, digits, unit] = width; + const amount = Number(digits); + const byPosition = !sign && !unit && workspaces.find((w) => w.focusedDisplay && w.position === amount); + const points = unit ? unit !== "%" : amount > 100; + const [goTo, moveWindow] = byPosition ? forWorkspace(byPosition) : []; + if (goTo) result.push(goTo); + // Defi clamps a width to at least 5% of the usable width. + if (sign ? amount > 0 : points || amount >= 5) { + result.push(sized("width", sign as "+" | "-" | undefined, amount, points)); + } + if (moveWindow) result.push(moveWindow); + } + + const height = text.match(/^h\s*([+-])?(\d{1,3})\s*%?$/i); + if (height && Number(height[2]) > 0 && Number(height[2]) <= 100) { + result.push(sized("height", height[1] as "+" | "-" | undefined, Number(height[2]), false)); + } + + // A partial name offers the two common actions; the exact name offers all five. + if (text.length >= minNameQuery) { + for (const workspace of workspaces) { + if (!workspace.name || !matchesName(workspace.name, text)) continue; + const all = forWorkspace(workspace); + result.push(...(workspace.name.toLowerCase() === text.toLowerCase() ? all : all.slice(0, 2))); + } + } + return result; +} diff --git a/contrib/raycast/src/window-list.tsx b/contrib/raycast/src/window-list.tsx new file mode 100644 index 00000000..f9097753 --- /dev/null +++ b/contrib/raycast/src/window-list.tsx @@ -0,0 +1,313 @@ +import { Action, ActionPanel, Color, environment, Icon, Keyboard, List } from "@raycast/api"; +import { showFailureToast, useCachedState, useFrecencySorting } from "@raycast/utils"; +import { useState } from "react"; +import { runAndClose } from "./defi"; +import { useDesktop } from "./desktop"; +import { ErrorView } from "./error-view"; +import { workspaceLabel, type DefiWindow, type DefiWorkspace } from "./parse"; +import { StripDetail } from "./strip-detail"; +import { stripColumns } from "./strip"; +import { workspaceAccessories, workspaceIcon } from "./workspace-icon"; + +const current = "current"; + +// Reads Defi before acting, reporting a failure where the user still sees it. +async function readOrReport(read: () => Promise): Promise { + try { + return await read(); + } catch (error) { + await showFailureToast(error, { title: "Cannot Reach Defi" }); + return undefined; + } +} + +// Where to move a window: cached workspaces at once, then a fresh read; a target that has gone +// in between fails with Defi's own message. +function MoveTargets({ + from, + subject, + onMove, +}: { + from?: string; + subject: string; + onMove: (workspace: DefiWorkspace) => void; +}) { + const { workspaces, isLoading, error, revalidate, displayCount, appIcon, appName } = useDesktop(); + const targets = error ? [] : (workspaces ?? []).filter((workspace) => workspace.id !== from); + const displays = [...new Set(targets.map((workspace) => workspace.display))]; + return ( + + {error ? : null} + {displays.map((display) => ( + 1 ? `Display ${display}` : undefined}> + {targets + .filter((workspace) => workspace.display === display) + .map((workspace) => ( + + onMove(workspace)} /> + + } + /> + ))} + + ))} + + ); +} + +// The window switcher; with workspaceID it lists that workspace only, as Search Workspaces opens it. +export function WindowList({ initialSearch = "", workspaceID }: { initialSearch?: string; workspaceID?: string }) { + const desktop = useDesktop(); + const { windows, workspaces, displayCount, appIcon, current: here } = desktop; + const [searchText, setSearchText] = useState(initialSearch); + const [showDetail, setShowDetail] = useCachedState("windows-strip", false); + // Every launch starts on all workspaces, so a narrowed list never surprises. + const [chosenScope, setScope] = useState(""); + const withWindows = workspaces?.filter((workspace) => workspace.windowCount > 0) ?? []; + const scope = workspaceID ?? chosenScope; + // shortcut: ranks windows by picks made here, not by Defi's focus history, and keeps the ranks of + // closed windows; use Defi's order once list-windows exposes it (feat/recent-windows-switcher keeps one). + const { data: ranked, visitItem } = useFrecencySorting(windows, { key: (window) => String(window.id) }); + const scopeID = scope === current ? here?.id : scope; + const scopeName = workspaces?.find((workspace) => workspace.id === scopeID); + // Cached rows would look live while Defi is unreachable, so an error hides them. + const inScope = desktop.error ? [] : ranked.filter((window) => !scopeID || window.workspace?.id === scopeID); + // The focused window goes last: Enter on it would change nothing. + const sorted = [...inScope.filter((window) => !window.focused), ...inScope.filter((window) => window.focused)]; + const dark = environment.appearance === "dark"; + + const position = (window: DefiWindow) => { + if (!window.workspace || !window.column) return "Floating"; + const columns = stripColumns(windows ?? [], window.workspace.id); + const stack = columns[window.column - 1]?.length ?? 1; + const place = `Column ${window.column} of ${columns.length}`; + return stack > 1 ? `${place}, row ${window.row} of ${stack}` : place; + }; + + // shortcut: acting on a window first focuses it, which shows its workspace; a CLI command + // that takes a window ID would avoid that switch. + const act = async (window: DefiWindow, then: string[][] = [], hud?: string) => { + await visitItem(window); + await runAndClose([["focus-window-id", String(window.id)], ...then], hud); + }; + + const scopeMenu = workspaceID ? undefined : ( + + + {here ? : null} + + {withWindows.map((workspace) => ( + 1 + ? `${workspaceLabel(workspace)} on Display ${workspace.display}` + : workspaceLabel(workspace) + } + value={workspace.id} + icon={workspaceIcon(workspace)} + /> + ))} + + + ); + + const emptyView = () => { + if (desktop.error) return ; + if (desktop.isLoading) return null; + if (scopeID && !searchText) { + const label = scopeName ? workspaceLabel(scopeName) : "This Workspace"; + return ( + + setScope("")} /> + + ) + } + /> + ); + } + return ( + + ); + }; + + return ( + 0} + navigationTitle={workspaceID && scopeName ? `Windows on ${workspaceLabel(scopeName)}` : undefined} + searchBarPlaceholder="Search windows by title, app, or workspace" + searchBarAccessory={scopeMenu} + searchText={searchText} + onSearchTextChange={setSearchText} + filtering + > + {emptyView()} + {sorted.map((window) => { + const workspace = window.workspace; + const label = workspace ? workspaceLabel(workspace) : undefined; + const fullWorkspace = workspaces?.find((candidate) => candidate.id === workspace?.id); + return ( + 1 ? [`display ${window.display}`] : []), + ]} + // Rows next to the side pane stay bare; the pane carries the same facts. + accessories={ + showDetail + ? undefined + : [ + ...(window.focused ? [{ tag: { value: "Focused", color: Color.Blue } }] : []), + ...(window.floating ? [{ tag: "Floating" }] : []), + // A list scoped to one workspace would repeat its name on every row. + ...(fullWorkspace && label && !scopeID + ? [ + { + icon: workspaceIcon(fullWorkspace), + text: label, + tooltip: displayCount > 1 ? `${label} on Display ${window.display}` : label, + }, + ] + : []), + ] + } + detail={ + + {window.title ? : null} + + {label ? : null} + {displayCount > 1 ? ( + + ) : null} + + {window.focused ? ( + + + + ) : null} + + } + actions={ + + + act(window)} /> + {/* A pushed list, not a submenu: some launchers flatten submenus, which would give ⌘↵ to its first entry. */} + + act( + window, + [["move-window-to-workspace-name", target.id]], + `Moved ${window.appName} to ${workspaceLabel(target)}`, + ) + } + /> + } + /> + {here && workspace && here.id !== workspace.id ? ( + { + // The cached current workspace may be stale while the list reloads. + const state = await readOrReport(desktop.fresh); + if (!state) return; + const latest = state.workspaces.find((w) => w.active && w.focusedDisplay); + const target = latest && latest.id !== workspace.id ? latest : undefined; + await act( + window, + target ? [["move-window-to-workspace-name", target.id]] : [], + target ? `Brought ${window.appName} to ${workspaceLabel(target)}` : undefined, + ); + }} + /> + ) : null} + + act(window, [["toggle-floating"]], `${window.floating ? "Tiled" : "Floated"} ${window.appName}`) + } + /> + + + + + + + setShowDetail(!showDetail)} + /> + + + + } + /> + ); + })} + + ); +} diff --git a/contrib/raycast/src/workspace-icon.ts b/contrib/raycast/src/workspace-icon.ts new file mode 100644 index 00000000..6818260b --- /dev/null +++ b/contrib/raycast/src/workspace-icon.ts @@ -0,0 +1,72 @@ +import { Color, Icon, type Image, type List } from "@raycast/api"; + +// Raycast icons for the SF Symbols most often set in [workspaces] icons. +const symbols: Record = { + curlybraces: Icon.Code, + "chevron.left.forwardslash.chevron.right": Icon.Code, + terminal: Icon.Terminal, + hammer: Icon.Hammer, + "wrench.and.screwdriver": Icon.WrenchScrewdriver, + ladybug: Icon.Bug, + ant: Icon.Bug, + globe: Icon.Globe, + safari: Icon.Globe, + message: Icon.Message, + bubble: Icon.Bubble, + envelope: Icon.Envelope, + calendar: Icon.Calendar, + music: Icon.Music, + headphones: Icon.Headphones, + video: Icon.Video, + photo: Icon.Image, + camera: Icon.Camera, + paintbrush: Icon.Brush, + pencil: Icon.Pencil, + doc: Icon.Document, + book: Icon.Book, + folder: Icon.Folder, + tray: Icon.Tray, + gearshape: Icon.Gear, + gear: Icon.Gear, + cloud: Icon.Cloud, + star: Icon.Star, + heart: Icon.Heart, + house: Icon.House, + person: Icon.Person, + lock: Icon.Lock, + gamecontroller: Icon.GameController, + cart: Icon.Cart, + leaf: Icon.Leaf, + bolt: Icon.Bolt, + airplane: Icon.Airplane, + desktopcomputer: Icon.Desktop, + display: Icon.Monitor, +}; + +type Workspace = { position: number; icon?: string; kind?: string }; + +// The workspace's configured symbol, or its position as a number; tinted when active. +export function workspaceIcon(workspace: Workspace, active = false): Image.ImageLike { + if (workspace.kind === "trailing") return Icon.Plus; + const symbol = workspace.icon?.replace(/\.(fill|circle|square)$/g, ""); + const number = Icon[`Number${String(workspace.position).padStart(2, "0")}` as keyof typeof Icon]; + const source = (symbol && (symbols[symbol] ?? symbols[symbol.split(".")[0]])) || number || Icon.AppWindowGrid3x3; + return active ? { source, tintColor: Color.Blue } : source; +} + +const maxAppIcons = 5; + +// A workspace row's accessories: up to five app icons, a count of the rest, and the Active tag. +export function workspaceAccessories( + workspace: { applications: string[]; active: boolean }, + appIcon: (bundleID?: string) => Image.ImageLike | undefined, + appName: (bundleID: string) => string, +): List.Item.Accessory[] { + const icons = workspace.applications.map((app) => ({ icon: appIcon(app) ?? Icon.AppWindow, tooltip: appName(app) })); + const extra = icons.length - maxAppIcons; + return [ + ...icons.slice(0, maxAppIcons), + ...(extra > 0 ? [{ text: `+${extra}` }] : []), + ...(workspace.active ? [{ tag: { value: "Active", color: Color.Blue } }] : []), + ]; +} diff --git a/contrib/raycast/test/commands.test.mts b/contrib/raycast/test/commands.test.mts new file mode 100644 index 00000000..077a7715 --- /dev/null +++ b/contrib/raycast/test/commands.test.mts @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { test } from "node:test"; +import { describeCommand, prompts, sections } from "../src/commands.ts"; + +// The catalog teaches commands and default shortcuts, so it must match the reference it copies: +// Defi's CONFIGURATION.md, from DEFI_CONFIGURATION or a Defi checkout around this folder. +const referencePath = process.env.DEFI_CONFIGURATION ?? new URL("../../../CONFIGURATION.md", import.meta.url).pathname; +const skip = existsSync(referencePath) ? false : "set DEFI_CONFIGURATION to Defi's CONFIGURATION.md"; +const rows = skip + ? [] + : readFileSync(referencePath, "utf8") + .split("\n") + .filter((line) => line.startsWith("| `")); +const entries = sections.flatMap(([, list]) => list); + +test("every catalog command is a documented command with a description", { skip }, () => { + for (const { command } of entries) { + const verb = command.split(" ")[0]; + assert.ok( + rows.some((row) => row.startsWith(`| \`${verb} `) || row.startsWith(`| \`${verb}\``)), + `${verb} is not in CONFIGURATION.md`, + ); + assert.match(describeCommand(command), /^[A-Z].+\.$/, `${command} has no description`); + } + assert.ok(prompts.every((prompt) => sections.some(([section]) => section === prompt.section))); + // Each row says what it does itself, not what its whole command family does. + const texts = entries.map((entry) => describeCommand(entry.command)); + assert.equal(new Set(texts).size, texts.length, "two rows share a description"); +}); + +test("every default shortcut matches the documented one", { skip }, () => { + for (const { command, shortcut } of entries.filter((entry) => entry.shortcut)) { + const [verb, direction = ""] = command.split(" "); + // A command can have one row per group of arguments, such as left|right and first|last. + const row = + rows.find((line) => { + const cell = line.split("`")[1] ?? ""; + return cell.split(" ")[0] === verb && (!direction || cell.includes(direction)); + }) ?? ""; + // The reference writes the four directions of one binding as . + const general = shortcut!.replace(new RegExp(`-${direction}$`), "-"); + assert.ok( + row.includes(`\`${shortcut}\``) || row.includes(`\`${general}\``), + `${command}: ${shortcut} is not its documented shortcut`, + ); + } +}); diff --git a/contrib/raycast/test/fixtures/list-windows.json b/contrib/raycast/test/fixtures/list-windows.json new file mode 100644 index 00000000..8e40dba9 --- /dev/null +++ b/contrib/raycast/test/fixtures/list-windows.json @@ -0,0 +1,209 @@ +{ + "version": 1, + "windows": [ + { + "appID": "com.apple.mail", + "appName": "Mail", + "column": 1, + "display": 1, + "floating": false, + "focused": false, + "id": 112, + "monitorID": 1, + "row": 1, + "title": "Window 1", + "workspace": { + "id": "dev", + "name": "dev", + "position": 1 + } + }, + { + "appID": "com.t3tools.t3code", + "appName": "T3 Code (Nightly)", + "column": 2, + "display": 1, + "floating": false, + "focused": true, + "id": 2613, + "monitorID": 1, + "row": 1, + "title": "Window 2", + "workspace": { + "id": "dev", + "name": "dev", + "position": 1 + } + }, + { + "appID": "com.apple.finder", + "appName": "Finder", + "column": 3, + "display": 1, + "floating": false, + "focused": false, + "id": 46, + "monitorID": 1, + "row": 1, + "title": "Window 3", + "workspace": { + "id": "dev", + "name": "dev", + "position": 1 + } + }, + { + "appID": "com.apple.dt.Devices", + "appName": "Device Hub", + "column": 4, + "display": 1, + "floating": false, + "focused": false, + "id": 45, + "monitorID": 1, + "row": 1, + "title": "Window 4", + "workspace": { + "id": "dev", + "name": "dev", + "position": 1 + } + }, + { + "appID": "com.ardia.browser", + "appName": "Ardia", + "column": 1, + "display": 1, + "floating": false, + "focused": false, + "id": 51, + "monitorID": 1, + "row": 1, + "title": "Window 5", + "workspace": { + "id": "web", + "name": "web", + "position": 2 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 1, + "display": 1, + "floating": false, + "focused": false, + "id": 53, + "monitorID": 1, + "row": 1, + "title": "Window 6", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 2, + "display": 1, + "floating": false, + "focused": false, + "id": 59, + "monitorID": 1, + "row": 1, + "title": "Window 7", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 3, + "display": 1, + "floating": false, + "focused": false, + "id": 58, + "monitorID": 1, + "row": 1, + "title": "Window 8", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 4, + "display": 1, + "floating": false, + "focused": false, + "id": 57, + "monitorID": 1, + "row": 1, + "title": "Window 9", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 5, + "display": 1, + "floating": false, + "focused": false, + "id": 55, + "monitorID": 1, + "row": 1, + "title": "Window 10", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 6, + "display": 1, + "floating": false, + "focused": false, + "id": 54, + "monitorID": 1, + "row": 1, + "title": "Window 11", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + }, + { + "appID": "com.mitchellh.ghostty", + "appName": "Ghostty", + "column": 7, + "display": 1, + "floating": false, + "focused": false, + "id": 56, + "monitorID": 1, + "row": 1, + "title": "Window 12", + "workspace": { + "id": "dev-secondary", + "name": "dev-secondary", + "position": 3 + } + } + ] +} diff --git a/contrib/raycast/test/fixtures/list-workspaces.json b/contrib/raycast/test/fixtures/list-workspaces.json new file mode 100644 index 00000000..909250f3 --- /dev/null +++ b/contrib/raycast/test/fixtures/list-workspaces.json @@ -0,0 +1,68 @@ +{ + "focusedMonitorID": 1, + "monitors": [ + { + "activeWorkspace": "dev", + "display": 1, + "focused": true, + "id": 1, + "workspaces": [ + { + "active": true, + "applications": ["com.apple.dt.Devices", "com.apple.finder", "com.apple.mail", "com.t3tools.t3code"], + "focusedApplication": "com.t3tools.t3code", + "icon": "curlybraces", + "id": "dev", + "kind": "named", + "name": "dev", + "occupied": true, + "position": 1, + "windowCount": 4 + }, + { + "active": false, + "applications": ["com.ardia.browser"], + "icon": "globe", + "id": "web", + "kind": "named", + "name": "web", + "occupied": true, + "position": 2, + "windowCount": 1 + }, + { + "active": false, + "applications": ["com.mitchellh.ghostty"], + "icon": "hammer", + "id": "dev-secondary", + "kind": "named", + "name": "dev-secondary", + "occupied": true, + "position": 3, + "windowCount": 7 + }, + { + "active": false, + "applications": [], + "icon": "wrench.and.screwdriver", + "id": "tools", + "kind": "named", + "name": "tools", + "occupied": false, + "position": 4, + "windowCount": 0 + }, + { + "active": false, + "applications": [], + "id": "__defi_dynamic_5", + "kind": "trailing", + "occupied": false, + "position": 5, + "windowCount": 0 + } + ] + } + ], + "version": 2 +} diff --git a/contrib/raycast/test/parse.test.mts b/contrib/raycast/test/parse.test.mts new file mode 100644 index 00000000..2605d3c6 --- /dev/null +++ b/contrib/raycast/test/parse.test.mts @@ -0,0 +1,69 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; +import { parseWindows, parseWorkspaces, workspaceLabel } from "../src/parse.ts"; + +const fixture = (name: string) => readFileSync(new URL(`fixtures/${name}`, import.meta.url), "utf8"); + +test("parses list-windows --json in the order received", () => { + const windows = parseWindows(fixture("list-windows.json")); + assert.equal(windows.length, 12); + assert.deepEqual(windows[1], { + id: 2613, + appID: "com.t3tools.t3code", + appName: "T3 Code (Nightly)", + title: "Window 2", + workspace: { id: "dev", name: "dev", position: 1 }, + display: 1, + column: 2, + row: 1, + floating: false, + focused: true, + }); +}); + +test("parses list-workspaces --json across displays", () => { + const workspaces = parseWorkspaces(fixture("list-workspaces.json")); + assert.deepEqual( + workspaces.map((workspace) => [workspace.id, workspace.display, workspace.kind, workspace.active]), + [ + ["dev", 1, "named", true], + ["web", 1, "named", false], + ["dev-secondary", 1, "named", false], + ["tools", 1, "named", false], + ["__defi_dynamic_5", 1, "trailing", false], + ], + ); + assert.equal(workspaceLabel(workspaces[4]), "New Workspace"); + assert.equal(workspaceLabel({ position: 2 }), "Workspace 2"); +}); + +test("ignores unknown fields and malformed entries", () => { + const windows = parseWindows( + JSON.stringify({ + version: 1, + extra: true, + windows: [{ id: 7, title: "T", appName: "A", future: 1 }, { title: "no id" }], + }), + ); + assert.deepEqual(windows, [ + { + id: 7, + appID: undefined, + appName: "A", + title: "T", + workspace: undefined, + display: undefined, + column: undefined, + row: undefined, + floating: false, + focused: false, + }, + ]); +}); + +test("refuses an unknown format version", () => { + assert.throws(() => parseWindows('{"version":2,"windows":[]}'), /version 2/); + assert.throws(() => parseWorkspaces('{"version":3,"monitors":[]}'), /version 3/); + assert.throws(() => parseWorkspaces("not json")); +}); diff --git a/contrib/raycast/test/view.test.mts b/contrib/raycast/test/view.test.mts new file mode 100644 index 00000000..737cecc8 --- /dev/null +++ b/contrib/raycast/test/view.test.mts @@ -0,0 +1,95 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; +import { parseWindows, parseWorkspaces } from "../src/parse.ts"; +import { stripColumns, stripImage } from "../src/strip.ts"; +import { formatShortcut } from "../src/shortcut.ts"; +import { suggestions } from "../src/suggest.ts"; + +const fixture = (name: string) => readFileSync(new URL(`fixtures/${name}`, import.meta.url), "utf8"); +const windows = parseWindows(fixture("list-windows.json")); +const workspaces = parseWorkspaces(fixture("list-workspaces.json")); + +test("strip columns follow column and row, skipping floating windows", () => { + const stacked = [ + { id: 1, appName: "A", title: "", focused: false, workspace: { id: "w", position: 1 }, column: 2, row: 2 }, + { id: 2, appName: "B", title: "", focused: false, workspace: { id: "w", position: 1 }, column: 2, row: 1 }, + { id: 3, appName: "C", title: "", focused: false, workspace: { id: "w", position: 1 }, column: 1, row: 1 }, + { id: 4, appName: "D", title: "", focused: false, workspace: { id: "w", position: 1 } }, + ]; + assert.deepEqual( + stripColumns(stacked, "w").map((column) => column.map((window) => window.id)), + [[3], [2, 1]], + ); +}); + +test("the strip image draws one tile per tiled window and highlights one", () => { + const image = stripImage(windows, "dev", 2613, true); + const svg = Buffer.from(image.match(/^!\[Strip\]\(data:image\/svg\+xml;base64,([^)]+)\)$/)![1], "base64").toString(); + assert.equal(svg.match(/ { + const titles = (query: string) => suggestions(query, workspaces).map((s) => [s.title, s.args.join(" ")]); + assert.deepEqual(titles("60"), [["Set Column Width to 60%", "set-column-width 60%"]]); + assert.deepEqual(titles("3"), [ + ["Go to dev-secondary", "--monitor 1 workspace dev-secondary"], + ["Move Focused Window to dev-secondary", "move-window-to-workspace-name dev-secondary"], + ]); + assert.deepEqual(titles("7"), [["Set Column Width to 7%", "set-column-width 7%"]]); + assert.deepEqual(titles("+10"), [["Widen Column by 10%", "set-column-width +10%"]]); + assert.deepEqual(titles("-40pt"), [["Narrow Column by 40 pt", "set-column-width -40"]]); + assert.deepEqual(titles("960"), [["Set Column Width to 960 pt", "set-column-width 960"]]); + assert.deepEqual(titles("h50"), [["Set Window Height to 50%", "set-window-height 50%"]]); + assert.deepEqual(titles("h-10"), [["Shrink Window by 10%", "set-window-height -10%"]]); + // A partial name offers the two common actions, a word inside the name counts, one letter offers none. + assert.deepEqual(titles("we"), [ + ["Go to web", "--monitor 1 workspace web"], + ["Move Focused Window to web", "move-window-to-workspace-name web"], + ]); + assert.equal(titles("sec")[0][0], "Go to dev-secondary"); + assert.deepEqual(titles("w"), []); + assert.deepEqual( + titles("web").map(([title]) => title), + [ + "Go to web", + "Move Focused Window to web", + "Send Focused Window to web", + "Move Focused Column to web", + "Send Focused Column to web", + ], + ); + assert.deepEqual(titles("zzz"), []); + assert.deepEqual(titles(" "), []); +}); + +test("copied commands use the form [keys] accepts, and digit shortcuts only where Defi binds them", () => { + const bindings = (query: string) => suggestions(query, workspaces).map((s) => s.binding); + assert.deepEqual(bindings("web").slice(0, 2), ["workspace web", "move-window-to-workspace-name web"]); + assert.deepEqual(bindings("5"), [ + "focus-workspace-position 5", + "set-column-width 5%", + "move-window-to-workspace-position 5", + ]); + // Named workspaces take mod-1 … mod-4 in an order the CLI does not report; position 5 follows them. + const shortcuts = (query: string) => suggestions(query, workspaces).map((s) => s.shortcut); + assert.deepEqual(shortcuts("2"), [undefined, undefined]); + assert.deepEqual(shortcuts("5"), ["mod-5", undefined, undefined]); + assert.deepEqual( + suggestions("+10", workspaces).map((s) => s.hud), + ["Widened column by 10%"], + ); + assert.deepEqual( + suggestions("h-10", workspaces).map((s) => s.hud), + ["Shrank window by 10%"], + ); +}); + +test("shortcuts read as macOS glyphs with the default mod", () => { + assert.equal(formatShortcut("mod-left"), "⌃⌥←"); + assert.equal(formatShortcut("mod-shift-comma"), "⌃⌥⇧,"); + assert.equal(formatShortcut("mod-shift-cmd-up"), "⌃⌥⇧⌘↑"); + assert.equal(formatShortcut("mod-f"), "⌃⌥F"); +}); diff --git a/contrib/raycast/tsconfig.json b/contrib/raycast/tsconfig.json new file mode 100644 index 00000000..c2a1b8eb --- /dev/null +++ b/contrib/raycast/tsconfig.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*", "raycast-env.d.ts"], + "compilerOptions": { + "lib": ["es2023"], + "module": "commonjs", + "target": "es2022", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noEmit": true + } +}