diff --git a/extensions/vscrot/.gitignore b/extensions/vscrot/.gitignore new file mode 100644 index 00000000..eb3ae205 --- /dev/null +++ b/extensions/vscrot/.gitignore @@ -0,0 +1,2 @@ +node_modules +vicinae-env.d.ts diff --git a/extensions/vscrot/README.md b/extensions/vscrot/README.md new file mode 100644 index 00000000..ab3905ca --- /dev/null +++ b/extensions/vscrot/README.md @@ -0,0 +1,147 @@ +# VScrot + +A Vicinae extension for taking, annotating, and managing screenshots across Linux (Wayland & X11), macOS, and Windows. + +--- + +## Commands + +### `scrot` - Take a Screenshot + +The main command. Opens a list of capture modes filtered to what your active tool supports. After capture, a preview lets you save, copy to clipboard, annotate, or discard. + +**Capture modes:** + +| Mode | Description | +|---|---| +| Capture Area | Interactive region selection | +| Capture Window | Active or selected window | +| Capture Monitor | A single display | +| Capture All Monitors | Full desktop (all displays merged) | + +The **Active Tools** section at the bottom of the list shows which capture and annotation tools are currently in use. Tap either entry to switch to any other installed tool - the choice is saved and persists across sessions. + +### `manage-tools` - Manage Tools + +Shows all supported capture, annotation, clipboard, and dependency tools grouped by category, with installed/not-installed status. For each missing tool: + +- The install command for your detected package manager is shown inline +- **Copy Install Command** copies it to the clipboard +- **Show All Package Managers** shows a full install table for every supported manager +- **Copy Install Script for All Missing Tools** generates a combined script for everything not yet installed + +Supported package managers: `pacman`, `apt`, `dnf`, `brew`, `winget` + +--- + +## Capture Backends + +VScrot auto-detects the best available tool in priority order. You can override this in **Extension Preferences** or by tapping **Active Tools → Capture** inside the extension. + +### Wayland + +| Tool | Modes | Notes | Link | +|---|---|---|---| +| **grimblast** | area · window · monitor · full | Hyprland-native wrapper around grim. Recommended for Hyprland. | [hyprwm/contrib](https://github.com/hyprwm/contrib) | +| **grim** + slurp | area · window · monitor · full | Compositor-agnostic Wayland grabber. Requires `slurp` for selection and `jq` for window/monitor modes. | [grim](https://sr.ht/~emersion/grim/) · [slurp](https://github.com/emersion/slurp) | +| **spectacle** | area · window · monitor · full | KDE screenshot tool. Works on Wayland and X11. | [spectacle](https://apps.kde.org/spectacle/) | +| **gnome-screenshot** | area · window · full | GNOME screenshot utility. | [gnome-screenshot](https://gitlab.gnome.org/GNOME/gnome-screenshot) | +| **flameshot** | area · full | Cross-platform. Wayland support varies by compositor - works best on KDE/GNOME Wayland. | [flameshot.org](https://flameshot.org) | + +### X11 + +| Tool | Modes | Notes | Link | +|---|---|---|---| +| **maim** + slop | area · window · full | Recommended X11 tool. Requires `slop` for area selection and `xdotool` for window capture. | [maim](https://github.com/naelstrof/maim) · [slop](https://github.com/naelstrof/slop) | +| **scrot** | area · window · full | Classic X11 capture utility. | [scrot](https://github.com/resurrecting-open-source-projects/scrot) | + +### macOS + +| Tool | Modes | Notes | Link | +|---|---|---|---| +| **screencapture** | area · window · full | Built-in macOS utility - no installation required. | - | +| **screenshot-desktop** | full · monitor | npm package bundled with the extension. Uses native OS APIs on macOS and Windows. | [screenshot-desktop](https://github.com/bencevans/screenshot-desktop) | + +### Windows + +| Tool | Modes | Notes | Link | +|---|---|---|---| +| **screenshot-desktop** | full · monitor | npm package bundled with the extension. Uses Win32 GDI - no additional install needed. | [screenshot-desktop](https://github.com/bencevans/screenshot-desktop) | +| **flameshot** | area · full | Available for Windows via the official installer. | [flameshot.org](https://flameshot.org) | + +--- + +## Annotation Tools + +After capture, the preview offers an **Annotate** action using your active annotation tool. Tools are split into two modes: + +- **Auto-reload** - the tool is launched, blocks until you close it, then the preview refreshes automatically. +- **Manual save** - the tool opens in the background; save the file yourself, then use **Refresh Preview** inside the extension. + +| Tool | Mode | Platform | Link | +|---|---|---|---| +| **satty** | Auto-reload | Linux (Wayland) | [gabm/Satty](https://github.com/gabm/Satty) | +| **swappy** | Auto-reload | Linux (Wayland) | [jtheoof/swappy](https://github.com/jtheoof/swappy) | +| **GIMP** | Manual save | Linux · macOS · Windows | [gimp.org](https://www.gimp.org) | +| **Pinta** | Manual save | Linux · macOS · Windows | [pinta-project.com](https://www.pinta-project.com) | +| **Paint** (mspaint) | Manual save | Windows (built-in) | - | +| **None** | - | All | Disables the Annotate action | + +Auto-detection priority: satty → swappy → GIMP → Pinta → Paint + +--- + +## Clipboard Tools + +Images are copied to the clipboard using platform-native tools with automatic fallback: + +| Tool | Platform | Link | +|---|---|---| +| **wl-copy** (wl-clipboard) | Linux Wayland (primary) | [wl-clipboard](https://github.com/bugaevc/wl-clipboard) | +| **xclip** | Linux X11 (fallback) | [xclip](https://github.com/astrand/xclip) | +| **pbcopy** | macOS (built-in) | - | +| PowerShell clipboard | Windows (built-in) | - | + +--- + +## Dependencies + +These are not capture tools themselves but are required by certain backends: + +| Dependency | Required by | Link | +|---|---|---| +| **slurp** | grim (area · window · monitor modes) | [slurp](https://github.com/emersion/slurp) | +| **jq** | grim (window · monitor modes via hyprctl) | [jqlang/jq](https://github.com/jqlang/jq) | +| **slop** | maim (area selection) | [slop](https://github.com/naelstrof/slop) | +| **xdotool** | maim (window capture) | [xdotool](https://github.com/jordansissel/xdotool) | +| **ImageMagick** (`identify`) | Preview metadata (image dimensions) - optional | [imagemagick.org](https://imagemagick.org) | + +--- + +## Preferences + +Preferences are the default starting point. Active tool selections made inside the extension are saved per-session via LocalStorage and override preferences. + +| Preference | Description | Default | +|---|---|---| +| **Screenshot Tool** | Capture backend. `Auto-detect` picks the first available in priority order. | `auto` | +| **Annotation Tool** | Annotation tool. `Auto-detect` picks the first available. `None` disables annotation. | `auto` | +| **Screenshot Path** | Base folder where screenshots are saved. | `~/Pictures/Screenshots` | +| **Subfolder Format** | `strftime`-style subfolder name (e.g. `%d-%m-%Y`). Leave blank to save directly in the base folder. | `%d-%m-%Y` | +| **Filename Format** | `strftime`-style filename (e.g. `Screenshot_%d-%m-%Y_%H-%M-%S`). | `Screenshot_%d-%m-%Y_%H-%M-%S` | +| **Copy to Clipboard** | Copy to clipboard automatically after capture. | `true` | +| **Save to File** | Save to file automatically after capture. | `true` | + +**Supported date tokens:** `%Y` `%m` `%d` `%H` `%M` `%S` + +--- + +## Development + +```bash +npm install +npm run dev # watch mode +npm run build # production bundle +npm run lint # type-check + lint +npm run format # biome format +``` diff --git a/extensions/vscrot/assets/extension_icon.png b/extensions/vscrot/assets/extension_icon.png new file mode 100644 index 00000000..68ff4f1a Binary files /dev/null and b/extensions/vscrot/assets/extension_icon.png differ diff --git a/extensions/vscrot/package-lock.json b/extensions/vscrot/package-lock.json new file mode 100644 index 00000000..cf00a108 --- /dev/null +++ b/extensions/vscrot/package-lock.json @@ -0,0 +1,3928 @@ +{ + "name": "vscrot", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vscrot", + "license": "MIT", + "dependencies": { + "@vicinae/api": "^0.20.15", + "screenshot-desktop": "^1.6.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.2", + "typescript": "^5.9.2" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.2.tgz", + "integrity": "sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.2", + "@biomejs/cli-darwin-x64": "2.3.2", + "@biomejs/cli-linux-arm64": "2.3.2", + "@biomejs/cli-linux-arm64-musl": "2.3.2", + "@biomejs/cli-linux-x64": "2.3.2", + "@biomejs/cli-linux-x64-musl": "2.3.2", + "@biomejs/cli-win32-arm64": "2.3.2", + "@biomejs/cli-win32-x64": "2.3.2" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz", + "integrity": "sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz", + "integrity": "sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz", + "integrity": "sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz", + "integrity": "sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz", + "integrity": "sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz", + "integrity": "sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz", + "integrity": "sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz", + "integrity": "sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jgoz/esbuild-plugin-typecheck": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jgoz/esbuild-plugin-typecheck/-/esbuild-plugin-typecheck-4.0.4.tgz", + "integrity": "sha512-ca38NAWnE/GchWjO5m7Wbny+yMOsYkoJOboQGheCjnnu5uDxqQWJSIegN+C+CWl8K/1naI/cMfTrAfDH1oRoVQ==", + "license": "MIT", + "peerDependencies": { + "@jgoz/esbuild-plugin-livereload": ">=2.1.4", + "esbuild": ">=0.25.0", + "typescript": ">= 3.5" + }, + "peerDependenciesMeta": { + "@jgoz/esbuild-plugin-livereload": { + "optional": true + } + } + }, + "node_modules/@oclif/core": { + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.5.tgz", + "integrity": "sha512-qcdCF7NrdWPfme6Kr34wwljRCXbCVpL1WVxiNy0Ep6vbWKjxAjFQwuhqkoyL0yjI+KdwtLcOCGn5z2yzdijc8w==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-help": { + "version": "6.2.44", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.44.tgz", + "integrity": "sha512-x03Se2LtlOOlGfTuuubt5C4Z8NHeR4zKXtVnfycuLU+2VOMu2WpsGy9nbs3nYuInuvsIY1BizjVaTjUz060Sig==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-plugins": { + "version": "5.4.60", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-5.4.60.tgz", + "integrity": "sha512-aTunE6bxVNDGnxwK2RkTo8m5ghFEcD2M35gM/uTC35LgQ1k4PLFYZ3yWfjhrK0LmryrL921+Cr+jwH4nzmlAWg==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4.8.0", + "ansis": "^3.17.0", + "debug": "^4.4.0", + "npm": "^10.9.7", + "npm-package-arg": "^11.0.3", + "npm-run-path": "^5.3.0", + "object-treeify": "^4.0.1", + "semver": "^7.7.4", + "validate-npm-package-name": "^5.0.1", + "which": "^4.0.0", + "yarn": "^1.22.22" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@vicinae/api": { + "version": "0.20.15", + "resolved": "https://registry.npmjs.org/@vicinae/api/-/api-0.20.15.tgz", + "integrity": "sha512-7FAUgdQJ+D8krDfiAfXmxKMtqo3a/oLxC4F6IA14WSCc6Wh1NWqwNyBvmPJxxrpuNg1/eQcPfHaWqm1rZCzCMA==", + "license": "ISC", + "dependencies": { + "@jgoz/esbuild-plugin-typecheck": "^4.0.3", + "@oclif/core": "^4", + "@oclif/plugin-help": "^6", + "@oclif/plugin-plugins": "^5", + "@types/node": ">=18", + "@types/react": "19.0.10", + "chokidar": "^4.0.3", + "esbuild": "^0.25.2", + "react": "19.0.0", + "zod": "^4.0.17" + }, + "bin": { + "vici": "bin/run.js" + }, + "peerDependencies": { + "@types/node": ">=18", + "@types/react": "19.0.10" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "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/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/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/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/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/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-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-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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/npm": { + "version": "10.9.8", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.8.tgz", + "integrity": "sha512-fYwb6ODSmHkqrJQQaCxY3M2lPf/mpgC7ik0HSzzIwG5CGtabRp4bNqikatvCoT42b5INQSqudVH0R7yVmC9hVg==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.5", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.3", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.5", + "libnpmexec": "^9.0.5", + "libnpmfund": "^6.0.5", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.5", + "libnpmpublish": "^10.0.2", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.9", + "minipass": "^7.1.3", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.5.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.1", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.2", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.4", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^7.5.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.2", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^7.5.10" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "2.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.6.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.4.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.2", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.3", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/fdir": { + "version": "6.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.5.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^7.5.11" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.9", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minizlib": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.5.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^7.5.10" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/picomatch": { + "version": "4.0.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.23", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "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/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "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/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "7.5.11", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "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/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-treeify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-4.0.1.tgz", + "integrity": "sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/screenshot-desktop": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/screenshot-desktop/-/screenshot-desktop-1.15.4.tgz", + "integrity": "sha512-nvYzQH+pzA9cJVKxZzde4mX1ymZoPETUp3vT5vl72460782i+ZctSbV71vu+YU/9ktAEa9nONLRLpgliXgB5LA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/bencevans" + } + ], + "license": "MIT", + "dependencies": { + "temp": "^0.9.4" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "license": "MIT", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/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==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "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/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yarn": { + "version": "1.22.22", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz", + "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/extensions/vscrot/package.json b/extensions/vscrot/package.json new file mode 100644 index 00000000..785e0939 --- /dev/null +++ b/extensions/vscrot/package.json @@ -0,0 +1,223 @@ +{ + "$schema": "https://raw.githubusercontent.com/vicinaehq/vicinae/refs/heads/main/extra/schemas/extension.json", + "name": "vscrot", + "title": "VScrot", + "description": "An extension to take and manage screenshots", + "categories": [ + "Productivity", + "Other" + ], + "license": "MIT", + "author": "JaINTP", + "contributors": [], + "pastContributors": [], + "icon": "extension_icon.png", + "commands": [ + { + "name": "scrot", + "title": "Scrot", + "subtitle": "Take a screenshot", + "description": "Take a screenshot", + "mode": "view" + }, + { + "name": "area", + "title": "Capture Area", + "subtitle": "Capture a selected area of your screen.", + "description": "Capture a selected area of your screen.", + "mode": "view" + }, + { + "name": "window", + "title": "Capture Window", + "subtitle": "Capture a selected window.", + "description": "Capture a selected window.", + "mode": "view" + }, + { + "name": "monitor", + "title": "Capture Monitor", + "subtitle": "Capture a selected monitor.", + "description": "Capture a selected monitor.", + "mode": "view" + }, + { + "name": "all", + "title": "Capture All Monitors", + "subtitle": "Capture all monitors.", + "description": "Capture all monitors.", + "mode": "view" + }, + { + "name": "manage-tools", + "title": "Manage Tools", + "subtitle": "Check and install screenshot tools", + "description": "View which capture, annotation, and clipboard tools are installed and copy install commands for any that are missing", + "mode": "view" + } + ], + "preferences": [ + { + "name": "screenshot_tool", + "type": "dropdown", + "title": "Screenshot Tool", + "description": "The tool used to capture screenshots. 'Auto-detect' picks the first available one.", + "default": "auto", + "required": true, + "data": [ + { + "title": "Auto-detect", + "value": "auto" + }, + { + "title": "grimblast (Hyprland)", + "value": "grimblast" + }, + { + "title": "grim + slurp (Wayland)", + "value": "grim" + }, + { + "title": "spectacle (KDE)", + "value": "spectacle" + }, + { + "title": "gnome-screenshot (GNOME)", + "value": "gnome-screenshot" + }, + { + "title": "flameshot", + "value": "flameshot" + }, + { + "title": "maim + slop (X11)", + "value": "maim" + }, + { + "title": "scrot (X11)", + "value": "scrot" + }, + { + "title": "screencapture (macOS)", + "value": "screencapture" + }, + { + "title": "screenshot-desktop (npm, Windows/macOS)", + "value": "screenshot-desktop" + } + ] + }, + { + "name": "annotation_tool", + "type": "dropdown", + "title": "Annotation Tool", + "description": "Tool used to annotate screenshots. Auto-reload tools (Satty, swappy) reload the preview automatically. Manual tools (GIMP, Pinta, Paint) require you to save and use Refresh Preview.", + "default": "auto", + "required": true, + "data": [ + { + "title": "Auto-detect", + "value": "auto" + }, + { + "title": "Satty (auto-reload)", + "value": "satty" + }, + { + "title": "swappy (auto-reload)", + "value": "swappy" + }, + { + "title": "GIMP (manual save)", + "value": "gimp" + }, + { + "title": "Pinta (manual save)", + "value": "pinta" + }, + { + "title": "Paint / mspaint (manual save)", + "value": "mspaint" + }, + { + "title": "None", + "value": "none" + } + ] + }, + { + "name": "screenshot_path", + "type": "textfield", + "title": "Screenshot Path", + "description": "Folder to save screenshots to", + "default": "~/Pictures/Screenshots", + "required": true + }, + { + "name": "use_editor", + "type": "checkbox", + "title": "Use Editor (Satty)", + "label": "Open in editor by default", + "description": "Whether to open Satty after taking a screenshot", + "default": true, + "required": false + }, + { + "name": "copy_to_clipboard", + "type": "checkbox", + "title": "Copy to Clipboard", + "label": "Copy to clipboard automatically", + "description": "Whether to copy the screenshot to the clipboard", + "default": true, + "required": false + }, + { + "name": "save_to_file", + "type": "checkbox", + "title": "Save to File", + "label": "Save to file automatically", + "description": "Whether to save the screenshot to a file", + "default": true, + "required": false + }, + { + "name": "subfolder_format", + "type": "textfield", + "title": "Subfolder Format", + "description": "Folder name format (e.g. %d-%m-%Y). Omit to save directly in screenshot path.", + "default": "%d-%m-%Y", + "required": false + }, + { + "name": "filename_format", + "type": "textfield", + "title": "Filename Format", + "description": "File name format (e.g. Screenshot_%d-%m-%Y_%H-%M-%S)", + "default": "Screenshot_%d-%m-%Y_%H-%M-%S", + "required": true + }, + { + "name": "autoclose_vicinae", + "type": "checkbox", + "title": "Autoclose Vicinae", + "label": "Close Vicinae after saving or copying", + "description": "Automatically close Vicinae after saving a screenshot to file or copying it to the clipboard", + "default": false, + "required": false + } + ], + "scripts": { + "build": "vici build", + "dev": "vici develop", + "format": "biome format --write src", + "lint": "vici lint" + }, + "dependencies": { + "@vicinae/api": "^0.20.15", + "screenshot-desktop": "^1.6.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.2", + "typescript": "^5.9.2" + } +} diff --git a/extensions/vscrot/src/all.tsx b/extensions/vscrot/src/all.tsx new file mode 100644 index 00000000..64a43ad6 --- /dev/null +++ b/extensions/vscrot/src/all.tsx @@ -0,0 +1,87 @@ +import { useState, useEffect } from "react"; +import { closeMainWindow } from "@vicinae/api"; +import { getPrefs } from "./lib/preferences"; +import { formatDateTokens } from "./lib/dateFormat"; +import { TEMP_PATH, getSavePath, saveImageFile } from "./lib/filesystem"; +import { copyToClipboard } from "./lib/clipboard"; +import { annotateWith } from "./lib/annotate"; +import { captureScreenshot } from "./lib/capture"; +import { getAnnotator } from "./annotators"; +import { resolveBackend, resolveAnnotator } from "./lib/tool-selection"; +import { PreviewDetail } from "./components/PreviewDetail"; + +export default function CaptureAll() { + const prefs = getPrefs(); + const [lastCapture, setLastCapture] = useState(null); + const [activeBackendId, setActiveBackendId] = useState(null); + const [activeAnnotatorId, setActiveAnnotatorId] = useState( + null, + ); + + const refreshPreview = () => { + setLastCapture(null); + setTimeout(() => setLastCapture(TEMP_PATH), 100); + }; + + const capture = async (backendId: string) => { + const result = await captureScreenshot("full", backendId); + if (result) setLastCapture(result); + }; + + useEffect(() => { + Promise.all([ + resolveBackend(prefs.screenshot_tool ?? "auto"), + resolveAnnotator(prefs.annotation_tool ?? "auto"), + ]).then(([backend, annotator]) => { + const bid = backend?.id ?? null; + setActiveBackendId(bid); + setActiveAnnotatorId(annotator?.id ?? null); + if (bid) capture(bid); + }); + }, []); + + const activeAnnotator = activeAnnotatorId + ? getAnnotator(activeAnnotatorId) + : null; + const annotatorLabel = + activeAnnotator && activeAnnotator.id !== "none" + ? `Annotate (${activeAnnotator.displayName})` + : null; + + const handleSave = () => { + if (!lastCapture) return; + saveImageFile(lastCapture, getSavePath(prefs)); + setLastCapture(null); + if (prefs.autoclose_vicinae) closeMainWindow(); + }; + + const handleAnnotate = async () => { + if (!lastCapture || !activeAnnotatorId) return; + const shouldReload = await annotateWith(lastCapture, activeAnnotatorId); + if (shouldReload) refreshPreview(); + }; + + if (!lastCapture) return null; + + return ( + copyToClipboard(lastCapture, prefs.autoclose_vicinae)} + onAnnotate={handleAnnotate} + onRefreshPreview={refreshPreview} + onReshoot={() => { + setLastCapture(null); + if (activeBackendId) capture(activeBackendId); + }} + onDiscard={() => setLastCapture(null)} + /> + ); +} diff --git a/extensions/vscrot/src/annotators/gimp.ts b/extensions/vscrot/src/annotators/gimp.ts new file mode 100644 index 00000000..b5467da1 --- /dev/null +++ b/extensions/vscrot/src/annotators/gimp.ts @@ -0,0 +1,17 @@ +import { exec } from "node:child_process"; +import type { AnnotatorBackend } from "./types"; +import { isCommandAvailable, shellEscape } from "../backends/utils"; + +export const gimpAnnotator: AnnotatorBackend = { + id: "gimp", + displayName: "GIMP", + mode: "manual", + + isAvailable: () => isCommandAvailable("gimp"), + + annotate: async (imagePath: string) => { + await new Promise((resolve) => { + exec(`gimp "${shellEscape(imagePath)}"`, () => resolve()); + }); + }, +}; diff --git a/extensions/vscrot/src/annotators/index.ts b/extensions/vscrot/src/annotators/index.ts new file mode 100644 index 00000000..16118844 --- /dev/null +++ b/extensions/vscrot/src/annotators/index.ts @@ -0,0 +1,24 @@ +import { sattyAnnotator } from "./satty"; +import { swappyAnnotator } from "./swappy"; +import { gimpAnnotator } from "./gimp"; +import { pintaAnnotator } from "./pinta"; +import { mspaintAnnotator } from "./mspaint"; +import { noneAnnotator } from "./none"; +import type { AnnotatorBackend } from "./types"; + +export type { AnnotatorBackend, AnnotationMode } from "./types"; + +// Priority order for auto-detection (auto-reload tools first) +export const ALL_ANNOTATORS: AnnotatorBackend[] = [ + sattyAnnotator, + swappyAnnotator, + gimpAnnotator, + pintaAnnotator, + mspaintAnnotator, +]; + +export const getAnnotator = (id: string): AnnotatorBackend | null => { + if (id === "none") return noneAnnotator; + if (id === "auto") return ALL_ANNOTATORS.find((a) => a.isAvailable()) ?? null; + return ALL_ANNOTATORS.find((a) => a.id === id) ?? null; +}; diff --git a/extensions/vscrot/src/annotators/mspaint.ts b/extensions/vscrot/src/annotators/mspaint.ts new file mode 100644 index 00000000..0795286d --- /dev/null +++ b/extensions/vscrot/src/annotators/mspaint.ts @@ -0,0 +1,17 @@ +import { exec } from "node:child_process"; +import type { AnnotatorBackend } from "./types"; +import { shellEscape } from "../backends/utils"; + +export const mspaintAnnotator: AnnotatorBackend = { + id: "mspaint", + displayName: "Paint", + mode: "manual", + + isAvailable: () => process.platform === "win32", + + annotate: async (imagePath: string) => { + await new Promise((resolve) => { + exec(`mspaint.exe "${shellEscape(imagePath)}"`, () => resolve()); + }); + }, +}; diff --git a/extensions/vscrot/src/annotators/none.ts b/extensions/vscrot/src/annotators/none.ts new file mode 100644 index 00000000..8363699a --- /dev/null +++ b/extensions/vscrot/src/annotators/none.ts @@ -0,0 +1,11 @@ +import type { AnnotatorBackend } from "./types"; + +export const noneAnnotator: AnnotatorBackend = { + id: "none", + displayName: "None", + mode: "manual", + + isAvailable: () => true, + + annotate: async (_imagePath: string) => {}, +}; diff --git a/extensions/vscrot/src/annotators/pinta.ts b/extensions/vscrot/src/annotators/pinta.ts new file mode 100644 index 00000000..7da0e0bd --- /dev/null +++ b/extensions/vscrot/src/annotators/pinta.ts @@ -0,0 +1,17 @@ +import { exec } from "node:child_process"; +import type { AnnotatorBackend } from "./types"; +import { isCommandAvailable, shellEscape } from "../backends/utils"; + +export const pintaAnnotator: AnnotatorBackend = { + id: "pinta", + displayName: "Pinta", + mode: "manual", + + isAvailable: () => isCommandAvailable("pinta"), + + annotate: async (imagePath: string) => { + await new Promise((resolve) => { + exec(`pinta "${shellEscape(imagePath)}"`, () => resolve()); + }); + }, +}; diff --git a/extensions/vscrot/src/annotators/satty.ts b/extensions/vscrot/src/annotators/satty.ts new file mode 100644 index 00000000..46b8115b --- /dev/null +++ b/extensions/vscrot/src/annotators/satty.ts @@ -0,0 +1,18 @@ +import { execSync } from "node:child_process"; +import type { AnnotatorBackend } from "./types"; +import { isCommandAvailable, shellEscape } from "../backends/utils"; + +export const sattyAnnotator: AnnotatorBackend = { + id: "satty", + displayName: "Satty", + mode: "auto", + + isAvailable: () => isCommandAvailable("satty"), + + annotate: async (imagePath: string) => { + const p = shellEscape(imagePath); + execSync( + `satty --filename "${p}" --output-filename "${p}" --early-exit`, + ); + }, +}; diff --git a/extensions/vscrot/src/annotators/swappy.ts b/extensions/vscrot/src/annotators/swappy.ts new file mode 100644 index 00000000..5d3fa5e2 --- /dev/null +++ b/extensions/vscrot/src/annotators/swappy.ts @@ -0,0 +1,16 @@ +import { execSync } from "node:child_process"; +import type { AnnotatorBackend } from "./types"; +import { isCommandAvailable, shellEscape } from "../backends/utils"; + +export const swappyAnnotator: AnnotatorBackend = { + id: "swappy", + displayName: "swappy", + mode: "auto", + + isAvailable: () => isCommandAvailable("swappy"), + + annotate: async (imagePath: string) => { + const p = shellEscape(imagePath); + execSync(`swappy -f "${p}" -o "${p}"`); + }, +}; diff --git a/extensions/vscrot/src/annotators/types.ts b/extensions/vscrot/src/annotators/types.ts new file mode 100644 index 00000000..98e1c017 --- /dev/null +++ b/extensions/vscrot/src/annotators/types.ts @@ -0,0 +1,10 @@ +export type AnnotationMode = "auto" | "manual"; + +export interface AnnotatorBackend { + readonly id: string; + readonly displayName: string; + /** "auto" = blocks until editor closes and writes output; "manual" = opens editor non-blocking */ + readonly mode: AnnotationMode; + isAvailable(): boolean; + annotate(imagePath: string): Promise; +} diff --git a/extensions/vscrot/src/area.tsx b/extensions/vscrot/src/area.tsx new file mode 100644 index 00000000..7bee30ab --- /dev/null +++ b/extensions/vscrot/src/area.tsx @@ -0,0 +1,87 @@ +import { useState, useEffect } from "react"; +import { closeMainWindow } from "@vicinae/api"; +import { getPrefs } from "./lib/preferences"; +import { formatDateTokens } from "./lib/dateFormat"; +import { TEMP_PATH, getSavePath, saveImageFile } from "./lib/filesystem"; +import { copyToClipboard } from "./lib/clipboard"; +import { annotateWith } from "./lib/annotate"; +import { captureScreenshot } from "./lib/capture"; +import { getAnnotator } from "./annotators"; +import { resolveBackend, resolveAnnotator } from "./lib/tool-selection"; +import { PreviewDetail } from "./components/PreviewDetail"; + +export default function CaptureArea() { + const prefs = getPrefs(); + const [lastCapture, setLastCapture] = useState(null); + const [activeBackendId, setActiveBackendId] = useState(null); + const [activeAnnotatorId, setActiveAnnotatorId] = useState( + null, + ); + + const refreshPreview = () => { + setLastCapture(null); + setTimeout(() => setLastCapture(TEMP_PATH), 100); + }; + + const capture = async (backendId: string) => { + const result = await captureScreenshot("area", backendId); + if (result) setLastCapture(result); + }; + + useEffect(() => { + Promise.all([ + resolveBackend(prefs.screenshot_tool ?? "auto"), + resolveAnnotator(prefs.annotation_tool ?? "auto"), + ]).then(([backend, annotator]) => { + const bid = backend?.id ?? null; + setActiveBackendId(bid); + setActiveAnnotatorId(annotator?.id ?? null); + if (bid) capture(bid); + }); + }, []); + + const activeAnnotator = activeAnnotatorId + ? getAnnotator(activeAnnotatorId) + : null; + const annotatorLabel = + activeAnnotator && activeAnnotator.id !== "none" + ? `Annotate (${activeAnnotator.displayName})` + : null; + + const handleSave = () => { + if (!lastCapture) return; + saveImageFile(lastCapture, getSavePath(prefs)); + setLastCapture(null); + if (prefs.autoclose_vicinae) closeMainWindow(); + }; + + const handleAnnotate = async () => { + if (!lastCapture || !activeAnnotatorId) return; + const shouldReload = await annotateWith(lastCapture, activeAnnotatorId); + if (shouldReload) refreshPreview(); + }; + + if (!lastCapture) return null; + + return ( + copyToClipboard(lastCapture, prefs.autoclose_vicinae)} + onAnnotate={handleAnnotate} + onRefreshPreview={refreshPreview} + onReshoot={() => { + setLastCapture(null); + if (activeBackendId) capture(activeBackendId); + }} + onDiscard={() => setLastCapture(null)} + /> + ); +} diff --git a/extensions/vscrot/src/backends/flameshot.ts b/extensions/vscrot/src/backends/flameshot.ts new file mode 100644 index 00000000..e6440354 --- /dev/null +++ b/extensions/vscrot/src/backends/flameshot.ts @@ -0,0 +1,20 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, shellEscape } from "./utils"; + +export const flameshotBackend: CaptureBackend = { + id: "flameshot", + displayName: "flameshot", + supportedModes: ["area", "full"], + + isAvailable: () => isCommandAvailable("flameshot"), + + capture: async (mode: CaptureMode, outputPath: string) => { + const out = shellEscape(outputPath); + if (mode === "area") { + execSync(`flameshot gui --raw > "${out}"`); + } else { + execSync(`flameshot screen --raw > "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/backends/gnome-screenshot.ts b/extensions/vscrot/src/backends/gnome-screenshot.ts new file mode 100644 index 00000000..bbc39081 --- /dev/null +++ b/extensions/vscrot/src/backends/gnome-screenshot.ts @@ -0,0 +1,22 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, shellEscape } from "./utils"; + +export const gnomeScreenshotBackend: CaptureBackend = { + id: "gnome-screenshot", + displayName: "gnome-screenshot (GNOME)", + supportedModes: ["area", "window", "full"], + + isAvailable: () => isCommandAvailable("gnome-screenshot"), + + capture: async (mode: CaptureMode, outputPath: string) => { + const out = shellEscape(outputPath); + if (mode === "area") { + execSync(`gnome-screenshot -a -f "${out}"`); + } else if (mode === "window") { + execSync(`gnome-screenshot -w -f "${out}"`); + } else { + execSync(`gnome-screenshot -f "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/backends/grim.ts b/extensions/vscrot/src/backends/grim.ts new file mode 100644 index 00000000..994ff1fd --- /dev/null +++ b/extensions/vscrot/src/backends/grim.ts @@ -0,0 +1,34 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, selectMonitor, shellEscape } from "./utils"; + +export const grimBackend: CaptureBackend = { + id: "grim", + displayName: "grim + slurp (Wayland)", + supportedModes: ["area", "window", "monitor", "full"], + + isAvailable: () => isCommandAvailable("grim") && isCommandAvailable("slurp"), + + capture: async ( + mode: CaptureMode, + outputPath: string, + outputName?: string, + ) => { + const out = shellEscape(outputPath); + let geometry = ""; + if (mode === "area") { + geometry = execSync("slurp").toString().trim(); + } else if (mode === "window") { + geometry = execSync( + `hyprctl clients -j | jq -r '.[] | select(.mapped == true) | "\\(.at[0]),\\(.at[1]) \\(.size[0])x\\(.size[1])"' | slurp -r`, + ) + .toString() + .trim(); + } else if (mode === "monitor") { + const name = shellEscape(outputName ?? selectMonitor()); + execSync(`grim -o "${name}" "${out}"`); + return; + } + execSync(`grim ${geometry ? `-g "${shellEscape(geometry)}"` : ""} "${out}"`); + }, +}; diff --git a/extensions/vscrot/src/backends/grimblast.ts b/extensions/vscrot/src/backends/grimblast.ts new file mode 100644 index 00000000..a9a585f6 --- /dev/null +++ b/extensions/vscrot/src/backends/grimblast.ts @@ -0,0 +1,43 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, selectMonitor, shellEscape } from "./utils"; + +const MODE_MAP: Record = { + area: "area", + window: "active", + monitor: "output", + full: "screen", +}; + +export const grimblastBackend: CaptureBackend = { + id: "grimblast", + displayName: "grimblast (Hyprland)", + supportedModes: ["area", "window", "monitor", "full"], + + isAvailable: () => isCommandAvailable("grimblast"), + + capture: async ( + mode: CaptureMode, + outputPath: string, + outputName?: string, + ) => { + const out = shellEscape(outputPath); + if (mode === "monitor") { + // grimblast save output captures the focused output, which races with + // closeMainWindow() shifting focus. Use grim -o with named output instead. + const name = shellEscape(outputName ?? selectMonitor()); + execSync(`grim -o "${name}" "${out}"`); + } else if (mode === "window") { + // grimblast save active captures whatever window has focus after Vicinae closes. + // Use hyprctl + slurp for reliable interactive window selection instead. + const geometry = execSync( + `hyprctl clients -j | jq -r '.[] | select(.mapped == true) | "\\(.at[0]),\\(.at[1]) \\(.size[0])x\\(.size[1])"' | slurp -r`, + ) + .toString() + .trim(); + execSync(`grim -g "${shellEscape(geometry)}" "${out}"`); + } else { + execSync(`grimblast save ${MODE_MAP[mode]} "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/backends/index.ts b/extensions/vscrot/src/backends/index.ts new file mode 100644 index 00000000..6e8b5ba2 --- /dev/null +++ b/extensions/vscrot/src/backends/index.ts @@ -0,0 +1,32 @@ +import { grimBackend } from "./grim"; +import { grimblastBackend } from "./grimblast"; +import { x11ScrotBackend } from "./x11-scrot"; +import { maimBackend } from "./maim"; +import { flameshotBackend } from "./flameshot"; +import { spectacleBackend } from "./spectacle"; +import { gnomeScreenshotBackend } from "./gnome-screenshot"; +import { screencaptureBackend } from "./screencapture"; +import { screenshotDesktopBackend } from "./screenshot-desktop"; +import type { CaptureBackend } from "./types"; + +export type { CaptureBackend, CaptureMode } from "./types"; + +// Priority order for auto-detection - screenshot-desktop last (native tools preferred) +export const ALL_BACKENDS: CaptureBackend[] = [ + grimblastBackend, + grimBackend, + spectacleBackend, + gnomeScreenshotBackend, + flameshotBackend, + maimBackend, + x11ScrotBackend, + screencaptureBackend, + screenshotDesktopBackend, +]; + +export const getBackend = (id: string): CaptureBackend | null => { + if (id === "auto") { + return ALL_BACKENDS.find((b) => b.isAvailable()) ?? null; + } + return ALL_BACKENDS.find((b) => b.id === id) ?? null; +}; diff --git a/extensions/vscrot/src/backends/maim.ts b/extensions/vscrot/src/backends/maim.ts new file mode 100644 index 00000000..39eceded --- /dev/null +++ b/extensions/vscrot/src/backends/maim.ts @@ -0,0 +1,24 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, shellEscape } from "./utils"; + +export const maimBackend: CaptureBackend = { + id: "maim", + displayName: "maim + slop (X11)", + supportedModes: ["area", "window", "full"], + + isAvailable: () => isCommandAvailable("maim"), + + capture: async (mode: CaptureMode, outputPath: string) => { + const out = shellEscape(outputPath); + if (mode === "area") { + const geometry = execSync("slop -f '%x,%y %wx%h'").toString().trim(); + execSync(`maim -g "${shellEscape(geometry)}" "${out}"`); + } else if (mode === "window") { + const id = execSync("xdotool getactivewindow").toString().trim(); + execSync(`maim -i "${shellEscape(id)}" "${out}"`); + } else { + execSync(`maim "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/backends/screencapture.ts b/extensions/vscrot/src/backends/screencapture.ts new file mode 100644 index 00000000..c09f2d31 --- /dev/null +++ b/extensions/vscrot/src/backends/screencapture.ts @@ -0,0 +1,22 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { shellEscape } from "./utils"; + +export const screencaptureBackend: CaptureBackend = { + id: "screencapture", + displayName: "screencapture (macOS)", + supportedModes: ["area", "window", "full"], + + isAvailable: () => process.platform === "darwin", + + capture: async (mode: CaptureMode, outputPath: string) => { + const out = shellEscape(outputPath); + if (mode === "area") { + execSync(`screencapture -i "${out}"`); + } else if (mode === "window") { + execSync(`screencapture -w "${out}"`); + } else { + execSync(`screencapture "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/backends/screenshot-desktop.ts b/extensions/vscrot/src/backends/screenshot-desktop.ts new file mode 100644 index 00000000..cc1b4f91 --- /dev/null +++ b/extensions/vscrot/src/backends/screenshot-desktop.ts @@ -0,0 +1,26 @@ +import screenshot from "screenshot-desktop"; +import type { CaptureBackend, CaptureMode } from "./types"; + +// Uses native OS APIs: screencapture (macOS), Win32 GDI (Windows). +// On Linux it falls back to ImageMagick's `import` or scrot - prefer native backends there. +export const screenshotDesktopBackend: CaptureBackend = { + id: "screenshot-desktop", + displayName: "screenshot-desktop (npm)", + supportedModes: ["full", "monitor"], + + isAvailable: () => + process.platform === "win32" || process.platform === "darwin", + + capture: async (mode: CaptureMode, outputPath: string) => { + if (mode === "monitor") { + const displays = await screenshot.listDisplays(); + await screenshot({ + screen: displays[0]?.id, + filename: outputPath, + format: "png", + }); + } else { + await screenshot({ filename: outputPath, format: "png" }); + } + }, +}; diff --git a/extensions/vscrot/src/backends/spectacle.ts b/extensions/vscrot/src/backends/spectacle.ts new file mode 100644 index 00000000..71df95d5 --- /dev/null +++ b/extensions/vscrot/src/backends/spectacle.ts @@ -0,0 +1,22 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, shellEscape } from "./utils"; + +const FLAG_MAP: Record = { + area: "-r", + window: "-a", + monitor: "-m", + full: "-f", +}; + +export const spectacleBackend: CaptureBackend = { + id: "spectacle", + displayName: "spectacle (KDE)", + supportedModes: ["area", "window", "monitor", "full"], + + isAvailable: () => isCommandAvailable("spectacle"), + + capture: async (mode: CaptureMode, outputPath: string) => { + execSync(`spectacle ${FLAG_MAP[mode]} -b -o "${shellEscape(outputPath)}"`); + }, +}; diff --git a/extensions/vscrot/src/backends/types.ts b/extensions/vscrot/src/backends/types.ts new file mode 100644 index 00000000..c5f67bc8 --- /dev/null +++ b/extensions/vscrot/src/backends/types.ts @@ -0,0 +1,13 @@ +export type CaptureMode = "area" | "window" | "monitor" | "full"; + +export interface CaptureBackend { + readonly id: string; + readonly displayName: string; + readonly supportedModes: CaptureMode[]; + isAvailable(): boolean; + capture( + mode: CaptureMode, + outputPath: string, + outputName?: string, + ): Promise; +} diff --git a/extensions/vscrot/src/backends/utils.ts b/extensions/vscrot/src/backends/utils.ts new file mode 100644 index 00000000..eab7e455 --- /dev/null +++ b/extensions/vscrot/src/backends/utils.ts @@ -0,0 +1,48 @@ +import { execSync } from "node:child_process"; + +/** Escape a path for safe use inside double-quoted shell strings. */ +export const shellEscape = (p: string): string => p.replace(/"/g, '\\"'); + +export const isCommandAvailable = (cmd: string): boolean => { + try { + execSync(`command -v ${cmd}`, { stdio: "ignore" }); + return true; + } catch { + return false; + } +}; + +type HyprMonitor = { + x: number; + y: number; + width: number; + height: number; + scale: number; + transform: number; + name: string; +}; + +const logicalGeom = (m: HyprMonitor): string => { + const swapped = m.transform % 2 === 1; + const w = swapped + ? Math.floor(m.height / m.scale) + : Math.floor(m.width / m.scale); + const h = swapped + ? Math.floor(m.width / m.scale) + : Math.floor(m.height / m.scale); + return `${m.x},${m.y} ${w}x${h}`; +}; + +// Presents all monitors via slurp and returns the Wayland output name of the +// one the user selected. Using the name with `grim -o` lets grim handle +// transform/scale/rotation natively, avoiding geometry math bugs. +export const selectMonitor = (): string => { + const monitors: HyprMonitor[] = JSON.parse( + execSync("hyprctl monitors -j").toString(), + ); + const regions = monitors.map(logicalGeom).join("\n"); + const selected = execSync("slurp -r", { input: regions }).toString().trim(); + return ( + monitors.find((m) => logicalGeom(m) === selected)?.name ?? monitors[0].name + ); +}; diff --git a/extensions/vscrot/src/backends/x11-scrot.ts b/extensions/vscrot/src/backends/x11-scrot.ts new file mode 100644 index 00000000..1130ca83 --- /dev/null +++ b/extensions/vscrot/src/backends/x11-scrot.ts @@ -0,0 +1,22 @@ +import { execSync } from "node:child_process"; +import type { CaptureBackend, CaptureMode } from "./types"; +import { isCommandAvailable, shellEscape } from "./utils"; + +export const x11ScrotBackend: CaptureBackend = { + id: "scrot", + displayName: "scrot (X11)", + supportedModes: ["area", "window", "full"], + + isAvailable: () => isCommandAvailable("scrot"), + + capture: async (mode: CaptureMode, outputPath: string) => { + const out = shellEscape(outputPath); + if (mode === "area") { + execSync(`scrot -s "${out}"`); + } else if (mode === "window") { + execSync(`scrot -u "${out}"`); + } else { + execSync(`scrot "${out}"`); + } + }, +}; diff --git a/extensions/vscrot/src/components/CaptureList.tsx b/extensions/vscrot/src/components/CaptureList.tsx new file mode 100644 index 00000000..ac718709 --- /dev/null +++ b/extensions/vscrot/src/components/CaptureList.tsx @@ -0,0 +1,201 @@ +import { + Action, + ActionPanel, + Icon, + List, + openExtensionPreferences, +} from "@vicinae/api"; +import path from "node:path"; +import type { CaptureBackend } from "../backends/types"; +import type { AnnotatorBackend } from "../annotators/types"; +import type { CaptureMode } from "../backends/types"; +import { BackendPicker, AnnotatorPicker } from "./ToolPicker"; + +interface CaptureListProps { + recentFiles: string[]; + supportedModes: CaptureMode[]; + activeBackend: CaptureBackend | null; + activeAnnotator: AnnotatorBackend | null; + installedBackends: CaptureBackend[]; + installedAnnotators: AnnotatorBackend[]; + onCapture: (mode: CaptureMode) => void; + onRefresh: () => void; + onCopyFile: (filePath: string) => void; + onDeleteFile: (filePath: string) => void; + onSelectBackend: (id: string) => void; + onSelectAnnotator: (id: string) => void; +} + +const CAPTURE_ITEMS: { mode: CaptureMode; title: string; icon: Icon }[] = [ + { mode: "area", title: "Capture Area", icon: Icon.Crop }, + { mode: "window", title: "Capture Window", icon: Icon.AppWindow }, + { mode: "monitor", title: "Capture Monitor", icon: Icon.Monitor }, + { mode: "full", title: "Capture All Monitors", icon: Icon.Layers }, +]; + +export function CaptureList({ + recentFiles, + supportedModes, + activeBackend, + activeAnnotator, + installedBackends, + installedAnnotators, + onCapture, + onRefresh, + onCopyFile, + onDeleteFile, + onSelectBackend, + onSelectAnnotator, +}: CaptureListProps) { + const noTools = installedBackends.length === 0; + + return ( + + + + } + > + {noTools ? ( + + + + } + /> + ) : ( + <> + + {CAPTURE_ITEMS.filter((item) => + supportedModes.includes(item.mode), + ).map((item) => ( + + onCapture(item.mode)} + /> + + } + /> + ))} + + + + 1 + ? `${installedBackends.length} tools available` + : "1 tool available" + } + actions={ + + + } + /> + + + } + /> + + + } + /> + + + } + /> + + + {recentFiles.length > 0 && ( + + {recentFiles.map((file) => ( + + + + onCopyFile(file)} + /> + onDeleteFile(file)} + /> + + } + /> + ))} + + )} + + )} + + ); +} diff --git a/extensions/vscrot/src/components/PreviewDetail.tsx b/extensions/vscrot/src/components/PreviewDetail.tsx new file mode 100644 index 00000000..3befd72c --- /dev/null +++ b/extensions/vscrot/src/components/PreviewDetail.tsx @@ -0,0 +1,97 @@ +import { Action, ActionPanel, Detail, Icon } from "@vicinae/api"; +import { execSync } from "node:child_process"; +import { shellEscape } from "../backends/utils"; + +interface PreviewDetailProps { + imagePath: string; + suggestedName: string; + subfolder: string; + annotatorLabel: string | null; + onSave: () => void; + onCopy: () => void; + onAnnotate: () => void; + onRefreshPreview: () => void; + onReshoot: () => void; + onDiscard: () => void; +} + +export function PreviewDetail({ + imagePath, + suggestedName, + subfolder, + annotatorLabel, + onSave, + onCopy, + onAnnotate, + onRefreshPreview, + onReshoot, + onDiscard, +}: PreviewDetailProps) { + let dimensions = "unknown"; + try { + dimensions = execSync(`identify -format "%wx%h" "${shellEscape(imagePath)}"`) + .toString() + .trim(); + } catch { + // imagemagick not installed - skip + } + + return ( + + + + + {annotatorLabel && ( + + )} + + + + + + + + } + metadata={ + + + + + + + } + /> + ); +} diff --git a/extensions/vscrot/src/components/ToolPicker.tsx b/extensions/vscrot/src/components/ToolPicker.tsx new file mode 100644 index 00000000..c4cbc662 --- /dev/null +++ b/extensions/vscrot/src/components/ToolPicker.tsx @@ -0,0 +1,98 @@ +import { Action, ActionPanel, Icon, List, pop } from "@vicinae/api"; +import type { CaptureBackend } from "../backends/types"; +import type { AnnotatorBackend } from "../annotators/types"; + +interface BackendPickerProps { + available: CaptureBackend[]; + currentId: string | null; + onSelect: (id: string) => void; +} + +export function BackendPicker({ + available, + currentId, + onSelect, +}: BackendPickerProps) { + return ( + + {available.map((backend) => ( + + { + onSelect(backend.id); + pop(); + }} + /> + + } + /> + ))} + + ); +} + +interface AnnotatorPickerProps { + available: AnnotatorBackend[]; + currentId: string | null; + onSelect: (id: string) => void; +} + +export function AnnotatorPicker({ + available, + currentId, + onSelect, +}: AnnotatorPickerProps) { + return ( + + + { + onSelect("none"); + pop(); + }} + /> + + } + /> + {available.map((annotator) => ( + + { + onSelect(annotator.id); + pop(); + }} + /> + + } + /> + ))} + + ); +} diff --git a/extensions/vscrot/src/lib/annotate.ts b/extensions/vscrot/src/lib/annotate.ts new file mode 100644 index 00000000..914305df --- /dev/null +++ b/extensions/vscrot/src/lib/annotate.ts @@ -0,0 +1,43 @@ +import { exec } from "node:child_process"; +import { closeMainWindow, showHUD, showToast } from "@vicinae/api"; +import { getAnnotator } from "../annotators"; + +/** + * Returns true if the preview should be reloaded (auto-mode annotator), + * false if the user is saving manually or an error occurred. + */ +export const annotateWith = async ( + imagePath: string, + toolId: string, +): Promise => { + const annotator = getAnnotator(toolId); + if (!annotator || annotator.id === "none") return false; + + if (annotator.mode === "auto") { + await closeMainWindow(); + try { + await annotator.annotate(imagePath); + } catch (e) { + console.error("Annotator failed", e); + exec("vicinae open"); + showHUD(`${annotator.displayName} failed or was cancelled`); + return false; + } + exec("vicinae open"); + return true; + } + + // Manual-save: open the editor in the background, stay in Vicinae + try { + annotator.annotate(imagePath); + } catch (e) { + console.error("Failed to launch annotator", e); + showHUD(`Failed to open ${annotator.displayName}`); + return false; + } + showToast({ + title: `Opened in ${annotator.displayName}`, + message: "Save the file there, then use Refresh Preview to reload.", + }); + return false; +}; diff --git a/extensions/vscrot/src/lib/capture.ts b/extensions/vscrot/src/lib/capture.ts new file mode 100644 index 00000000..b9f7d614 --- /dev/null +++ b/extensions/vscrot/src/lib/capture.ts @@ -0,0 +1,42 @@ +import { exec } from "node:child_process"; +import { unlinkSync, existsSync } from "node:fs"; +import { closeMainWindow, showHUD } from "@vicinae/api"; +import { TEMP_PATH } from "./filesystem"; +import { getBackend } from "../backends"; +import type { CaptureMode } from "../backends/types"; + +export const captureScreenshot = async ( + mode: CaptureMode, + toolId: string, + delay = 0, + outputName?: string, +): Promise => { + const backend = getBackend(toolId); + + if (!backend) { + showHUD( + `No screenshot tool found (configured: ${toolId}). Install one and retry.`, + ); + return null; + } + + if (!backend.supportedModes.includes(mode)) { + showHUD(`${backend.displayName} does not support "${mode}" capture`); + return null; + } + + try { + if (existsSync(TEMP_PATH)) unlinkSync(TEMP_PATH); + await closeMainWindow(); + if (delay > 0) { + await new Promise((resolve) => setTimeout(resolve, delay * 1000)); + } + await backend.capture(mode, TEMP_PATH, outputName); + exec("vicinae open"); + return TEMP_PATH; + } catch { + // User cancelled selection (e.g. ESC in slurp) or tool error - surface nothing + exec("vicinae open"); + return null; + } +}; diff --git a/extensions/vscrot/src/lib/clipboard.ts b/extensions/vscrot/src/lib/clipboard.ts new file mode 100644 index 00000000..40b60e69 --- /dev/null +++ b/extensions/vscrot/src/lib/clipboard.ts @@ -0,0 +1,25 @@ +import { Clipboard, closeMainWindow, showHUD, showToast } from "@vicinae/api"; + +export const copyTextToClipboard = async (text: string): Promise => { + try { + await Clipboard.copy(text); + } catch (e) { + console.error("Failed to copy text to clipboard", e); + showHUD("Clipboard copy failed"); + } +}; + +export const copyToClipboard = async ( + imagePath: string, + autoClose = true, +): Promise => { + try { + await Clipboard.copy({ file: imagePath }); + showToast({ title: "Copied to clipboard" }); + } catch (e) { + console.error("Failed to copy image to clipboard", e); + showHUD("Clipboard copy failed"); + return; + } + if (autoClose) closeMainWindow(); +}; diff --git a/extensions/vscrot/src/lib/dateFormat.ts b/extensions/vscrot/src/lib/dateFormat.ts new file mode 100644 index 00000000..7778cdd4 --- /dev/null +++ b/extensions/vscrot/src/lib/dateFormat.ts @@ -0,0 +1,16 @@ +export const formatDateTokens = (format: string): string => { + const date = new Date(); + const tokens: Record = { + "%Y": date.getFullYear().toString(), + "%m": (date.getMonth() + 1).toString().padStart(2, "0"), + "%d": date.getDate().toString().padStart(2, "0"), + "%H": date.getHours().toString().padStart(2, "0"), + "%M": date.getMinutes().toString().padStart(2, "0"), + "%S": date.getSeconds().toString().padStart(2, "0"), + }; + let result = format; + for (const [token, value] of Object.entries(tokens)) { + result = result.split(token).join(value); + } + return result; +}; diff --git a/extensions/vscrot/src/lib/filesystem.ts b/extensions/vscrot/src/lib/filesystem.ts new file mode 100644 index 00000000..b07facbc --- /dev/null +++ b/extensions/vscrot/src/lib/filesystem.ts @@ -0,0 +1,57 @@ +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { showToast } from "@vicinae/api"; +import type { Preferences } from "./preferences"; +import { expandPath } from "./preferences"; +import { formatDateTokens } from "./dateFormat"; + +export const TEMP_PATH = path.join(os.tmpdir(), "vscrot_last.png"); + +export const getSavePath = ( + prefs: Preferences, + customFilename?: string, +): string => { + const base = expandPath(prefs.screenshot_path || "~/Pictures/Screenshots"); + const subfolder = prefs.subfolder_format + ? formatDateTokens(prefs.subfolder_format) + : ""; + const filename = customFilename || formatDateTokens(prefs.filename_format); + return path.join(base, subfolder, `${filename}.png`); +}; + +export const loadRecentFiles = (saveDirBase: string): string[] => { + if (!fs.existsSync(saveDirBase)) return []; + try { + const allFiles: { path: string; mtime: number }[] = []; + const walk = (dir: string) => { + if (!fs.existsSync(dir)) return; + for (const item of fs.readdirSync(dir)) { + const fullPath = path.join(dir, item); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + walk(fullPath); + } else if (item.endsWith(".png") || item.endsWith(".jpg")) { + allFiles.push({ path: fullPath, mtime: stat.mtimeMs }); + } + } + }; + walk(saveDirBase); + return allFiles + .sort((a, b) => b.mtime - a.mtime) + .slice(0, 20) + .map((f) => f.path); + } catch (e) { + console.error("Failed to load recent files", e); + return []; + } +}; + +export const saveImageFile = (sourcePath: string, destPath: string): void => { + const dir = path.dirname(destPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.copyFileSync(sourcePath, destPath); + showToast({ title: "Saved", message: path.basename(destPath) }); +}; diff --git a/extensions/vscrot/src/lib/preferences.ts b/extensions/vscrot/src/lib/preferences.ts new file mode 100644 index 00000000..89db5de7 --- /dev/null +++ b/extensions/vscrot/src/lib/preferences.ts @@ -0,0 +1,18 @@ +import { getPreferenceValues } from "@vicinae/api"; +import os from "node:os"; + +export interface Preferences { + screenshot_path: string; + screenshot_tool: string; + annotation_tool: string; + use_editor: boolean; + copy_to_clipboard: boolean; + save_to_file: boolean; + subfolder_format: string; + filename_format: string; + autoclose_vicinae: boolean; +} + +export const getPrefs = (): Preferences => getPreferenceValues(); + +export const expandPath = (p: string): string => p.replace(/^~/, os.homedir()); diff --git a/extensions/vscrot/src/lib/tool-selection.ts b/extensions/vscrot/src/lib/tool-selection.ts new file mode 100644 index 00000000..f771a649 --- /dev/null +++ b/extensions/vscrot/src/lib/tool-selection.ts @@ -0,0 +1,41 @@ +import { LocalStorage } from "@vicinae/api"; +import { ALL_BACKENDS, getBackend } from "../backends"; +import { ALL_ANNOTATORS, getAnnotator } from "../annotators"; +import type { CaptureBackend } from "../backends/types"; +import type { AnnotatorBackend } from "../annotators/types"; + +const CAPTURE_KEY = "active_capture_tool"; +const ANNOTATE_KEY = "active_annotation_tool"; + +export const getInstalledBackends = (): CaptureBackend[] => + ALL_BACKENDS.filter((b) => b.isAvailable()); + +export const getInstalledAnnotators = (): AnnotatorBackend[] => + ALL_ANNOTATORS.filter((a) => a.isAvailable()); + +/** Resolves the active backend: LocalStorage → preference → auto-detect. */ +export const resolveBackend = async ( + prefToolId: string, +): Promise => { + const saved = await LocalStorage.getItem(CAPTURE_KEY); + const id = saved ?? prefToolId; + const backend = getBackend(id); + // If the saved tool is no longer installed, fall back to auto-detect + if (backend && !backend.isAvailable()) return getBackend("auto"); + return backend; +}; + +/** Resolves the active annotator: LocalStorage → preference → auto-detect. */ +export const resolveAnnotator = async ( + prefToolId: string, +): Promise => { + const saved = await LocalStorage.getItem(ANNOTATE_KEY); + const id = saved ?? prefToolId; + return getAnnotator(id); +}; + +export const saveBackendChoice = (id: string): Promise => + LocalStorage.setItem(CAPTURE_KEY, id); + +export const saveAnnotatorChoice = (id: string): Promise => + LocalStorage.setItem(ANNOTATE_KEY, id); diff --git a/extensions/vscrot/src/lib/tools-database.ts b/extensions/vscrot/src/lib/tools-database.ts new file mode 100644 index 00000000..d3fe3015 --- /dev/null +++ b/extensions/vscrot/src/lib/tools-database.ts @@ -0,0 +1,278 @@ +import { execSync } from "node:child_process"; +import { isCommandAvailable } from "../backends/utils"; + +export type PackageManager = "pacman" | "apt" | "dnf" | "brew" | "winget"; + +export interface ToolInfo { + id: string; + displayName: string; + description: string; + category: "capture" | "annotate" | "clipboard" | "dependency"; + checkCommand: string | null; // null = bundled npm package, always present + packages: Partial>; + notes?: string; +} + +export const detectPackageManager = (): PackageManager | "unknown" => { + if (process.platform === "win32") return "winget"; + if (process.platform === "darwin") return "brew"; + if (isCommandAvailable("pacman")) return "pacman"; + if (isCommandAvailable("apt")) return "apt"; + if (isCommandAvailable("dnf")) return "dnf"; + return "unknown"; +}; + +export const getInstallCommand = ( + tool: ToolInfo, + pm: PackageManager, +): string | null => tool.packages[pm] ?? null; + +export const isToolInstalled = (tool: ToolInfo): boolean => { + if (tool.checkCommand === null) return true; + return isCommandAvailable(tool.checkCommand); +}; + +export const TOOLS_DATABASE: ToolInfo[] = [ + // ── Capture tools ──────────────────────────────────────────────────────── + { + id: "grimblast", + displayName: "grimblast", + description: "All-in-one capture tool for Hyprland (wraps grim + slurp)", + category: "capture", + checkCommand: "grimblast", + packages: { + pacman: "sudo pacman -S grimblast", + }, + }, + { + id: "grim", + displayName: "grim", + description: "Wayland image grabber - pair with slurp for area selection", + category: "capture", + checkCommand: "grim", + packages: { + pacman: "sudo pacman -S grim", + apt: "sudo apt install grim", + dnf: "sudo dnf install grim", + }, + }, + { + id: "spectacle", + displayName: "spectacle", + description: "KDE screenshot utility - works on Wayland and X11", + category: "capture", + checkCommand: "spectacle", + packages: { + pacman: "sudo pacman -S spectacle", + apt: "sudo apt install spectacle", + dnf: "sudo dnf install spectacle", + }, + }, + { + id: "gnome-screenshot", + displayName: "gnome-screenshot", + description: "GNOME screenshot utility", + category: "capture", + checkCommand: "gnome-screenshot", + packages: { + pacman: "sudo pacman -S gnome-screenshot", + apt: "sudo apt install gnome-screenshot", + dnf: "sudo dnf install gnome-utils", + }, + }, + { + id: "flameshot", + displayName: "flameshot", + description: "Cross-platform screenshot tool with built-in annotation GUI", + category: "capture", + checkCommand: "flameshot", + packages: { + pacman: "sudo pacman -S flameshot", + apt: "sudo apt install flameshot", + dnf: "sudo dnf install flameshot", + brew: "brew install flameshot", + winget: "winget install Ablaze.Flameshot", + }, + }, + { + id: "maim", + displayName: "maim", + description: + "Lightweight X11 screenshot tool - pair with slop for selection", + category: "capture", + checkCommand: "maim", + packages: { + pacman: "sudo pacman -S maim", + apt: "sudo apt install maim", + dnf: "sudo dnf install maim", + }, + }, + { + id: "scrot", + displayName: "scrot", + description: "Classic X11 screen capture utility", + category: "capture", + checkCommand: "scrot", + packages: { + pacman: "sudo pacman -S scrot", + apt: "sudo apt install scrot", + dnf: "sudo dnf install scrot", + brew: "brew install scrot", + }, + }, + { + id: "screenshot-desktop", + displayName: "screenshot-desktop", + description: + "npm package - uses native OS APIs (Windows/macOS only in this extension)", + category: "capture", + checkCommand: null, + packages: {}, + notes: "Bundled as an npm dependency - no installation required.", + }, + // ── Annotate tools ─────────────────────────────────────────────────────── + { + id: "satty", + displayName: "satty", + description: + "Modern Wayland-native screenshot annotation tool (auto-reload)", + category: "annotate", + checkCommand: "satty", + packages: { + pacman: "yay -S satty", + }, + notes: "AUR package - requires an AUR helper (yay, paru, etc.).", + }, + { + id: "swappy", + displayName: "swappy", + description: + "Wayland snapshot editing tool, common grim companion (auto-reload)", + category: "annotate", + checkCommand: "swappy", + packages: { + pacman: "sudo pacman -S swappy", + apt: "sudo apt install swappy", + }, + }, + { + id: "gimp", + displayName: "GIMP", + description: "Powerful cross-platform image editor (manual save)", + category: "annotate", + checkCommand: "gimp", + packages: { + pacman: "sudo pacman -S gimp", + apt: "sudo apt install gimp", + dnf: "sudo dnf install gimp", + brew: "brew install gimp", + winget: "winget install GIMP.GIMP", + }, + }, + { + id: "pinta", + displayName: "Pinta", + description: "Simple cross-platform paint/annotation tool (manual save)", + category: "annotate", + checkCommand: "pinta", + packages: { + pacman: "sudo pacman -S pinta", + apt: "sudo apt install pinta", + dnf: "sudo dnf install pinta", + brew: "brew install pinta", + }, + }, + // ── Clipboard tools ────────────────────────────────────────────────────── + { + id: "wl-clipboard", + displayName: "wl-clipboard", + description: + "Wayland clipboard utilities (wl-copy / wl-paste) - required on Wayland", + category: "clipboard", + checkCommand: "wl-copy", + packages: { + pacman: "sudo pacman -S wl-clipboard", + apt: "sudo apt install wl-clipboard", + dnf: "sudo dnf install wl-clipboard", + }, + }, + { + id: "xclip", + displayName: "xclip", + description: "X11 clipboard tool - fallback when wl-copy is unavailable", + category: "clipboard", + checkCommand: "xclip", + packages: { + pacman: "sudo pacman -S xclip", + apt: "sudo apt install xclip", + dnf: "sudo dnf install xclip", + }, + }, + // ── Dependencies ───────────────────────────────────────────────────────── + { + id: "slurp", + displayName: "slurp", + description: + "Wayland region selector - required by grim for area/window/monitor capture", + category: "dependency", + checkCommand: "slurp", + packages: { + pacman: "sudo pacman -S slurp", + apt: "sudo apt install slurp", + dnf: "sudo dnf install slurp", + }, + }, + { + id: "jq", + displayName: "jq", + description: + "JSON processor - required by grim for window/monitor selection via hyprctl", + category: "dependency", + checkCommand: "jq", + packages: { + pacman: "sudo pacman -S jq", + apt: "sudo apt install jq", + dnf: "sudo dnf install jq", + brew: "brew install jq", + winget: "winget install jqlang.jq", + }, + }, + { + id: "slop", + displayName: "slop", + description: "X11 region selector - required by maim for area selection", + category: "dependency", + checkCommand: "slop", + packages: { + pacman: "sudo pacman -S slop", + apt: "sudo apt install slop", + dnf: "sudo dnf install slop", + }, + }, + { + id: "xdotool", + displayName: "xdotool", + description: "X11 window tool - required by maim for window capture", + category: "dependency", + checkCommand: "xdotool", + packages: { + pacman: "sudo pacman -S xdotool", + apt: "sudo apt install xdotool", + dnf: "sudo dnf install xdotool", + }, + }, + { + id: "imagemagick", + displayName: "ImageMagick (identify)", + description: "Used to read image dimensions in the preview metadata", + category: "dependency", + checkCommand: "identify", + packages: { + pacman: "sudo pacman -S imagemagick", + apt: "sudo apt install imagemagick", + dnf: "sudo dnf install imagemagick", + brew: "brew install imagemagick", + winget: "winget install ImageMagick.ImageMagick", + }, + }, +]; diff --git a/extensions/vscrot/src/manage-tools.tsx b/extensions/vscrot/src/manage-tools.tsx new file mode 100644 index 00000000..655f4514 --- /dev/null +++ b/extensions/vscrot/src/manage-tools.tsx @@ -0,0 +1,146 @@ +import { Action, ActionPanel, Detail, Icon, List, showHUD } from "@vicinae/api"; +import { copyTextToClipboard } from "./lib/clipboard"; +import { + TOOLS_DATABASE, + detectPackageManager, + getInstallCommand, + isToolInstalled, + type ToolInfo, +} from "./lib/tools-database"; + +const CATEGORY_TITLES: Record = { + capture: "Capture Tools", + annotate: "Annotation Tools", + clipboard: "Clipboard Tools", + dependency: "Dependencies", +}; + +function InstallDetail({ + tool, + pm, +}: { + tool: ToolInfo; + pm: ReturnType; +}) { + const pms = ["pacman", "apt", "dnf", "brew", "winget"] as const; + const rows = pms + .map((p) => { + const cmd = tool.packages[p]; + return cmd ? `| \`${p}\` | \`${cmd}\` |` : null; + }) + .filter(Boolean) + .join("\n"); + + const notes = tool.notes ? `\n> **Note:** ${tool.notes}\n` : ""; + + const markdown = `# ${tool.displayName}\n\n${tool.description}\n${notes}\n## Install Commands\n\n| Package Manager | Command |\n|---|---|\n${rows || "| - | No package available |"}`; + + return ( + + {pm !== "unknown" && getInstallCommand(tool, pm) && ( + { + const cmd = getInstallCommand(tool, pm)!; + copyTextToClipboard(cmd); + showHUD(`Copied: ${cmd}`); + }} + /> + )} + + } + /> + ); +} + +export default function ManageTools() { + const pm = detectPackageManager(); + + const categories = [ + "capture", + "annotate", + "clipboard", + "dependency", + ] as const; + + const missingTools = TOOLS_DATABASE.filter( + (t) => !isToolInstalled(t) && t.checkCommand !== null, + ); + + const installScript = missingTools + .map((t) => + getInstallCommand(t, pm as Parameters[1]), + ) + .filter(Boolean) + .join("\n"); + + return ( + + {installScript && ( + { + copyTextToClipboard(installScript); + showHUD(`Copied install script (${missingTools.length} tools)`); + }} + /> + )} + + } + > + {categories.map((category) => { + const tools = TOOLS_DATABASE.filter((t) => t.category === category); + return ( + + {tools.map((tool) => { + const installed = isToolInstalled(tool); + const installCmd = + pm !== "unknown" ? getInstallCommand(tool, pm) : null; + + return ( + + {!installed && installCmd && ( + { + copyTextToClipboard(installCmd); + showHUD(`Copied: ${installCmd}`); + }} + /> + )} + } + /> + + } + /> + ); + })} + + ); + })} + + ); +} diff --git a/extensions/vscrot/src/monitor.tsx b/extensions/vscrot/src/monitor.tsx new file mode 100644 index 00000000..5b4b8205 --- /dev/null +++ b/extensions/vscrot/src/monitor.tsx @@ -0,0 +1,152 @@ +import { useState, useEffect } from "react"; +import { execSync } from "node:child_process"; +import { List, Action, ActionPanel, Icon, closeMainWindow } from "@vicinae/api"; +import { getPrefs } from "./lib/preferences"; +import { formatDateTokens } from "./lib/dateFormat"; +import { TEMP_PATH, getSavePath, saveImageFile } from "./lib/filesystem"; +import { copyToClipboard } from "./lib/clipboard"; +import { annotateWith } from "./lib/annotate"; +import { captureScreenshot } from "./lib/capture"; +import { getAnnotator } from "./annotators"; +import { resolveBackend, resolveAnnotator } from "./lib/tool-selection"; +import { PreviewDetail } from "./components/PreviewDetail"; + +type HyprMonitor = { name: string; description: string }; +type Phase = "loading" | "selecting" | "preview"; + +export default function CaptureMonitor() { + const prefs = getPrefs(); + const [phase, setPhase] = useState("loading"); + const [monitors, setMonitors] = useState([]); + const [lastCapture, setLastCapture] = useState(null); + const [activeBackendId, setActiveBackendId] = useState(null); + const [activeAnnotatorId, setActiveAnnotatorId] = useState( + null, + ); + + const refreshPreview = () => { + setLastCapture(null); + setTimeout(() => setLastCapture(TEMP_PATH), 100); + }; + + useEffect(() => { + (async () => { + const [backend, annotator] = await Promise.all([ + resolveBackend(prefs.screenshot_tool ?? "auto"), + resolveAnnotator(prefs.annotation_tool ?? "auto"), + ]); + + setActiveBackendId(backend?.id ?? null); + setActiveAnnotatorId(annotator?.id ?? null); + + try { + const parsed: Array<{ name: string; description: string }> = JSON.parse( + execSync("hyprctl monitors -j").toString(), + ); + if (parsed.length > 0) { + setMonitors( + parsed.map((m) => ({ name: m.name, description: m.description })), + ); + setPhase("selecting"); + return; + } + } catch { + // hyprctl unavailable — fall through to slurp-based capture + } + + // No monitor list available: fall back to slurp selection in the backend + if (backend) { + const result = await captureScreenshot("monitor", backend.id); + if (result) { + setLastCapture(result); + setPhase("preview"); + } + } + })(); + }, []); + + const captureByName = async (monitorName: string) => { + if (!activeBackendId) return; + setPhase("loading"); + const result = await captureScreenshot( + "monitor", + activeBackendId, + 0, + monitorName, + ); + if (result) { + setLastCapture(result); + setPhase("preview"); + } + }; + + const activeAnnotator = activeAnnotatorId + ? getAnnotator(activeAnnotatorId) + : null; + const annotatorLabel = + activeAnnotator && activeAnnotator.id !== "none" + ? `Annotate (${activeAnnotator.displayName})` + : null; + + const handleSave = () => { + if (!lastCapture) return; + saveImageFile(lastCapture, getSavePath(prefs)); + setLastCapture(null); + if (prefs.autoclose_vicinae) closeMainWindow(); + }; + + const handleAnnotate = async () => { + if (!lastCapture || !activeAnnotatorId) return; + const shouldReload = await annotateWith(lastCapture, activeAnnotatorId); + if (shouldReload) refreshPreview(); + }; + + if (phase === "preview" && lastCapture) { + return ( + copyToClipboard(lastCapture, prefs.autoclose_vicinae)} + onAnnotate={handleAnnotate} + onRefreshPreview={refreshPreview} + onReshoot={() => { + setLastCapture(null); + setPhase("selecting"); + }} + onDiscard={() => setLastCapture(null)} + /> + ); + } + + if (phase === "selecting") { + return ( + + {monitors.map((m) => ( + + captureByName(m.name)} + /> + + } + /> + ))} + + ); + } + + return null; +} diff --git a/extensions/vscrot/src/scrot.tsx b/extensions/vscrot/src/scrot.tsx new file mode 100644 index 00000000..27ff72f6 --- /dev/null +++ b/extensions/vscrot/src/scrot.tsx @@ -0,0 +1,186 @@ +import { useState, useEffect, useMemo } from "react"; +import { closeMainWindow } from "@vicinae/api"; +import fs from "node:fs"; +import { getPrefs, expandPath } from "./lib/preferences"; +import { formatDateTokens } from "./lib/dateFormat"; +import { + TEMP_PATH, + getSavePath, + loadRecentFiles, + saveImageFile, +} from "./lib/filesystem"; +import { copyToClipboard } from "./lib/clipboard"; +import { annotateWith } from "./lib/annotate"; +import { captureScreenshot } from "./lib/capture"; +import { getBackend } from "./backends"; +import { getAnnotator } from "./annotators"; +import { + getInstalledBackends, + getInstalledAnnotators, + resolveBackend, + resolveAnnotator, + saveBackendChoice, + saveAnnotatorChoice, +} from "./lib/tool-selection"; +import { PreviewDetail } from "./components/PreviewDetail"; +import { CaptureList } from "./components/CaptureList"; +import type { CaptureMode } from "./backends/types"; + +export default function Scrot() { + const prefs = getPrefs(); + const saveDirBase = expandPath( + prefs.screenshot_path || "~/Pictures/Screenshots", + ); + + // Detected synchronously at startup - stable across renders + const installedBackends = useMemo(() => getInstalledBackends(), []); + const installedAnnotators = useMemo(() => getInstalledAnnotators(), []); + + const [recentFiles, setRecentFiles] = useState([]); + const [lastCapture, setLastCapture] = useState(null); + + // Sync initial guess from prefs so first render is instant, then + // useEffect async-corrects from LocalStorage if a saved choice exists. + const [activeBackendId, setActiveBackendId] = useState(() => { + const b = getBackend(prefs.screenshot_tool ?? "auto"); + return b?.isAvailable() ? b.id : (installedBackends[0]?.id ?? null); + }); + const [activeAnnotatorId, setActiveAnnotatorId] = useState( + () => { + const a = getAnnotator(prefs.annotation_tool ?? "auto"); + return a ? a.id : (installedAnnotators[0]?.id ?? null); + }, + ); + + const refreshRecent = () => setRecentFiles(loadRecentFiles(saveDirBase)); + const refreshPreview = () => { + setLastCapture(null); + setTimeout(() => setLastCapture(TEMP_PATH), 100); + }; + + useEffect(() => { + refreshRecent(); + resolveBackend(prefs.screenshot_tool ?? "auto").then((b) => { + if (b) setActiveBackendId(b.id); + }); + resolveAnnotator(prefs.annotation_tool ?? "auto").then((a) => { + if (a) setActiveAnnotatorId(a.id); + }); + }, []); + + const activeBackend = activeBackendId ? getBackend(activeBackendId) : null; + const supportedModes: CaptureMode[] = activeBackend?.supportedModes ?? [ + "area", + "window", + "monitor", + "full", + ]; + + const activeAnnotator = activeAnnotatorId + ? getAnnotator(activeAnnotatorId) + : null; + const annotatorLabel = + activeAnnotator && activeAnnotator.id !== "none" + ? `Annotate (${activeAnnotator.displayName})` + : null; + + const capture = async (mode: CaptureMode) => { + if (!activeBackendId) return; + const result = await captureScreenshot(mode, activeBackendId); + if (!result) return; + + // Auto-annotate if the preference is set and an annotator is configured + if (prefs.use_editor && activeAnnotatorId) { + const shouldReload = await annotateWith(result, activeAnnotatorId); + if (shouldReload) { + // Auto-reload tools (Satty, swappy) wrote the file back — pick it up + // Fall through to auto-copy/save with the (now annotated) file + } + } + + // Auto-copy and/or auto-save if the preferences say so + if (prefs.copy_to_clipboard) await copyToClipboard(result, false); + if (prefs.save_to_file) { + saveImageFile(result, getSavePath(prefs)); + refreshRecent(); + } + + // Close if autoclose is on and at least one auto-action ran + if ((prefs.copy_to_clipboard || prefs.save_to_file) && prefs.autoclose_vicinae) { + closeMainWindow(); + return; + } + + setLastCapture(result); + }; + + const handleSave = () => { + if (!lastCapture) return; + saveImageFile(lastCapture, getSavePath(prefs)); + refreshRecent(); + setLastCapture(null); + if (prefs.autoclose_vicinae) closeMainWindow(); + }; + + const handleAnnotate = async () => { + if (!lastCapture || !activeAnnotatorId) return; + const shouldReload = await annotateWith(lastCapture, activeAnnotatorId); + if (shouldReload) refreshPreview(); + }; + + const handleSelectBackend = async (id: string) => { + await saveBackendChoice(id); + setActiveBackendId(id); + }; + + const handleSelectAnnotator = async (id: string) => { + await saveAnnotatorChoice(id); + setActiveAnnotatorId(id); + }; + + const handleDeleteFile = (filePath: string) => { + fs.unlinkSync(filePath); + refreshRecent(); + }; + + if (lastCapture) { + return ( + copyToClipboard(lastCapture, prefs.autoclose_vicinae)} + onAnnotate={handleAnnotate} + onRefreshPreview={refreshPreview} + onReshoot={() => { + setLastCapture(null); + capture("area"); + }} + onDiscard={() => setLastCapture(null)} + /> + ); + } + + return ( + + ); +} diff --git a/extensions/vscrot/src/window.tsx b/extensions/vscrot/src/window.tsx new file mode 100644 index 00000000..e9ff3adb --- /dev/null +++ b/extensions/vscrot/src/window.tsx @@ -0,0 +1,87 @@ +import { useState, useEffect } from "react"; +import { closeMainWindow } from "@vicinae/api"; +import { getPrefs } from "./lib/preferences"; +import { formatDateTokens } from "./lib/dateFormat"; +import { TEMP_PATH, getSavePath, saveImageFile } from "./lib/filesystem"; +import { copyToClipboard } from "./lib/clipboard"; +import { annotateWith } from "./lib/annotate"; +import { captureScreenshot } from "./lib/capture"; +import { getAnnotator } from "./annotators"; +import { resolveBackend, resolveAnnotator } from "./lib/tool-selection"; +import { PreviewDetail } from "./components/PreviewDetail"; + +export default function CaptureWindow() { + const prefs = getPrefs(); + const [lastCapture, setLastCapture] = useState(null); + const [activeBackendId, setActiveBackendId] = useState(null); + const [activeAnnotatorId, setActiveAnnotatorId] = useState( + null, + ); + + const refreshPreview = () => { + setLastCapture(null); + setTimeout(() => setLastCapture(TEMP_PATH), 100); + }; + + const capture = async (backendId: string) => { + const result = await captureScreenshot("window", backendId); + if (result) setLastCapture(result); + }; + + useEffect(() => { + Promise.all([ + resolveBackend(prefs.screenshot_tool ?? "auto"), + resolveAnnotator(prefs.annotation_tool ?? "auto"), + ]).then(([backend, annotator]) => { + const bid = backend?.id ?? null; + setActiveBackendId(bid); + setActiveAnnotatorId(annotator?.id ?? null); + if (bid) capture(bid); + }); + }, []); + + const activeAnnotator = activeAnnotatorId + ? getAnnotator(activeAnnotatorId) + : null; + const annotatorLabel = + activeAnnotator && activeAnnotator.id !== "none" + ? `Annotate (${activeAnnotator.displayName})` + : null; + + const handleSave = () => { + if (!lastCapture) return; + saveImageFile(lastCapture, getSavePath(prefs)); + setLastCapture(null); + if (prefs.autoclose_vicinae) closeMainWindow(); + }; + + const handleAnnotate = async () => { + if (!lastCapture || !activeAnnotatorId) return; + const shouldReload = await annotateWith(lastCapture, activeAnnotatorId); + if (shouldReload) refreshPreview(); + }; + + if (!lastCapture) return null; + + return ( + copyToClipboard(lastCapture, prefs.autoclose_vicinae)} + onAnnotate={handleAnnotate} + onRefreshPreview={refreshPreview} + onReshoot={() => { + setLastCapture(null); + if (activeBackendId) capture(activeBackendId); + }} + onDiscard={() => setLastCapture(null)} + /> + ); +} diff --git a/extensions/vscrot/tsconfig.json b/extensions/vscrot/tsconfig.json new file mode 100644 index 00000000..8d9b6bbc --- /dev/null +++ b/extensions/vscrot/tsconfig.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node 16", + "include": ["src/**/*"], + "compilerOptions": { + //"lib": ["es2020"], + "module": "commonjs", + "target": "es2020", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "jsx": "react-jsx", + "types": ["node"] + } +}