diff --git a/extensions/bitwarden/.prettierrc b/extensions/bitwarden/.prettierrc new file mode 100644 index 00000000..d7a6922c --- /dev/null +++ b/extensions/bitwarden/.prettierrc @@ -0,0 +1 @@ +{ "printWidth": 100, "trailingComma": "all", "singleQuote": false, "semi": true } diff --git a/extensions/bitwarden/README.md b/extensions/bitwarden/README.md new file mode 100644 index 00000000..fff9fe37 --- /dev/null +++ b/extensions/bitwarden/README.md @@ -0,0 +1,82 @@ +# Vicinae Bitwarden + +A native [Vicinae](https://vicinae.com) extension for [Bitwarden](https://bitwarden.com), backed by [`rbw`](https://github.com/doy/rbw) — the unofficial Rust Bitwarden CLI. `rbw-agent` keeps the unlocked vault in RAM, so commands respond in 50–200 ms instead of the ~4 s cold-start that the official `bw` CLI imposes. Works with the official Bitwarden cloud and self-hosted Bitwarden / Vaultwarden servers. + +## Requirements + +- Vicinae 0.16+ +- `rbw` 1.15+ on `$PATH` (or set the `cliPath` preference) + - Arch / CachyOS: `pacman -S rbw` + - Fedora: `dnf install rbw` + - Cargo: `cargo install rbw` +- A pinentry binary (`pinentry`, `pinentry-gtk2`, `pinentry-curses`, etc.) installed system-wide for any rbw use outside Vicinae. Inside Vicinae the extension swaps in its own pinentry shim during login/unlock and restores yours afterwards. +- Bun 1.x (for development) + +## Setup + +```sh +rbw config set email you@example.com +rbw config set base_url https://vault.example.com # only for self-hosted +``` + +1. Generate a personal API key at (Settings → Security → Keys → "View API Key"). +2. Open any extension command. On first launch you land on the Login form: enter email, API client ID, API client secret, and master password. The extension runs `rbw register` (device registration) and `rbw login` (vault unlock) for you. +3. Subsequent launches detect the already-unlocked rbw-agent and skip straight to Search Vault. The Lock Vault command tears the agent down; Log Out also purges the local rbw DB. + +### Self-signed certificates + +If your Bitwarden / Vaultwarden server uses a self-signed certificate, set the **Self-signed CA Bundle Path** preference to a PEM file containing the CA chain. The extension exposes it to rbw via `SSL_CERT_FILE`. + +## Commands + +| Command | What it does | +|---|---| +| Search Vault | Browse and act on items (Copy Password / Username / TOTP, Open URL, Show Details) | +| Authenticator | TOTP codes with live countdown | +| Generate Password | UI generator (chars / diceware) | +| Generate Password (Quick) | One-shot generate-and-copy, no UI | +| Create Login | Add a login item | +| Sync Vault | Pull latest vault state from the server | +| Lock Vault | Lock the rbw agent | +| Log Out | Tear down the agent and purge the local DB | + +## Removed features (vs. the bw-backed branch) + +- **Bitwarden Sends.** rbw does not support Sends. Create / Search / Receive Send commands are gone. +- **Create Folder.** rbw has no folder-creation primitive. Add an item with the desired folder name in **Create Login** — the folder appears once it contains an item. + +## Security + +- The master password is **never** persisted. +- `rbw-agent` holds the decrypted vault in RAM under your user account. The extension does **not** maintain its own session token; it asks the agent (`rbw unlocked`) on each refresh. +- The master password is handed to rbw via a small Assuan-protocol pinentry shim that reads from `RBW_PINENTRY_VALUE` (set per-process). The shim percent-encodes `%`, CR, and LF before emitting the value, per the GnuPG Assuan spec. +- The local vault cache contains only non-sensitive fields (item names, URIs, folder ids); passwords, notes, card numbers, identity fields, and TOTP secrets are **never** cached. Sensitive fields are fetched fresh via `rbw get --raw` on demand. +- Items flagged for reprompt require the master password again before sensitive actions; the grace window is configurable (`Reprompt Grace Window` preference). +- The pinentry config swap (extension shim → rbw default) is serialized through a process-wide mutex so concurrent rbw invocations cannot observe a transient pinentry. + +## Preferences + +- **API Client ID / Client Secret** (required) — used by `rbw register` for device registration. +- **Path to rbw CLI** — leave empty to use `$PATH`. +- **Self-hosted Server URL** — applied via `rbw config set base_url`. +- **Self-signed CA Bundle Path** — PEM file with your CA chain. Injected as `SSL_CERT_FILE`. +- **Sync on Launch** — run `rbw sync` after the first unlock of each session. +- **Fetch Favicons** — show favicons next to login items. +- **Reprompt Grace Window** — `Always reprompt` / 30s / 1m / 5m / 15m / `Never during session`. +- **Window Action on Copy** — close the launcher or keep it open after copying. +- **Cache Vault Items** — local cache of non-sensitive fields. + +## Development + +```sh +bun install +bun run dev # live reload via vici develop +bun test # unit tests (vitest) +bun run typecheck +bun run lint +bun run build # production build via vici +``` + +## License + +MIT diff --git a/extensions/bitwarden/assets/icon.png b/extensions/bitwarden/assets/icon.png new file mode 100644 index 00000000..e11e7cd0 Binary files /dev/null and b/extensions/bitwarden/assets/icon.png differ diff --git a/extensions/bitwarden/eslint.config.mjs b/extensions/bitwarden/eslint.config.mjs new file mode 100644 index 00000000..22a702bf --- /dev/null +++ b/extensions/bitwarden/eslint.config.mjs @@ -0,0 +1,22 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import globals from "globals"; + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.{ts,tsx,js,jsx}"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { ...globals.node, ...globals.browser, ...globals.es2022 }, + }, + rules: { + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + }, + }, + { + ignores: ["dist/", "node_modules/", "coverage/", "vicinae-env.d.ts"], + }, +); diff --git a/extensions/bitwarden/package-lock.json b/extensions/bitwarden/package-lock.json new file mode 100644 index 00000000..f10976bf --- /dev/null +++ b/extensions/bitwarden/package-lock.json @@ -0,0 +1,5933 @@ +{ + "name": "bitwarden", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bitwarden", + "license": "MIT", + "dependencies": { + "@vicinae/api": "^0.20.14", + "react": "^19.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.0", + "@types/node": "^22.10.0", + "@types/react": "^19.0.0", + "eslint": "^9.39.0", + "globals": "^17.3.0", + "prettier": "^3.4.0", + "typescript": "^5.7.0", + "typescript-eslint": "^8.21.0", + "vitest": "^3.2.0" + } + }, + "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/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@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/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@oclif/core": { + "version": "4.10.6", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.6.tgz", + "integrity": "sha512-ySCOYnPKZE3KACT1V9It99hWG9b8E5MpagbRdWxPNRO3beMqmbr4SLUQoFtZ9XRtW++kks1ZVwZOdpnR8rpb9A==", + "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.45", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.45.tgz", + "integrity": "sha512-avWOKYmjANtyu8ipju/kopIIrSrbS/scJjiZTpBp/HKEHNm46v5riOo5LQj6MZ4bYJVQEoyHPg/2Seig5Ilkjw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-plugins": { + "version": "5.4.62", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-5.4.62.tgz", + "integrity": "sha512-AFFKQpn/SHJE5ETiyQYBJjg7895WmvrBjEKvLWnJX+fNuOVSf6WLbiRqJI7kEaLUmmL8O1EtMcc18COxTOSgIw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4.8.0", + "ansis": "^3.17.0", + "debug": "^4.4.0", + "npm": "^11.13.0", + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vicinae/api": { + "version": "0.20.14", + "resolved": "https://registry.npmjs.org/@vicinae/api/-/api-0.20.14.tgz", + "integrity": "sha512-/++sx0J8JUWj9D7O74lM89QDEPUdxLxiWxFsQRVxq3LVxcEGbvkJHSyuBc+aVHuqfHSwRByXUoWYoY7LgexT2Q==", + "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/@vicinae/api/node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@vicinae/api/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/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "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/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "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/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.13.0.tgz", + "integrity": "sha512-cRmhaghDWA1lFgl3Ug4/VxDJdPBK/U+tNtnrl9kXunFqhWw1x4xL5txkNn7qzPuVfvXOmXyjHpMwsuk2uisbkg==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "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", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "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" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.4.3", + "@npmcli/config": "^10.8.1", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", + "abbrev": "^4.0.0", + "archy": "~1.0.0", + "cacache": "^20.0.4", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.4", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.6", + "libnpmexec": "^10.2.6", + "libnpmfund": "^7.0.20", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.6", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.5", + "minimatch": "^10.2.5", + "minipass": "^7.1.3", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^12.3.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.5.0", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", + "qrcode-terminal": "^0.12.0", + "read": "^5.0.1", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^2.0.2", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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": "4.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": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "10.8.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "7.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "10.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "4.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.5.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "4.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "4.0.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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/@tufjs/models": { + "version": "4.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "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": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/npm/node_modules/bin-links": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "5.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "20.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "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": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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": "5.0.4", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "2.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "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": "8.0.4", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/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": "13.0.6", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "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": "9.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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.7.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "8.2.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "6.0.4", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^5.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "4.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.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": "10.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "8.1.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.3", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "10.2.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.4.3", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "read": "^5.0.1", + "semver": "^7.3.7", + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.20", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "9.1.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.3", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "11.1.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7", + "sigstore": "^4.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "11.3.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "15.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "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/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": "5.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.6", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^7.1.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "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": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.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": "12.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "8.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "13.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "11.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "12.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "19.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "4.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^20.17.0 || >=22.9.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/pacote": { + "version": "21.5.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "2.0.2", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": "6.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.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/promzard": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^3.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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/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": "4.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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-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": "13.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "10.2.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "7.5.13", + "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": "2.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.16", + "inBundle": true, + "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/npm/node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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": "4.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/undici": { + "version": "6.25.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/which": { + "version": "6.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "7.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "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/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "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/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/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/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/bitwarden/package.json b/extensions/bitwarden/package.json new file mode 100644 index 00000000..d2b799f9 --- /dev/null +++ b/extensions/bitwarden/package.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://www.vicinae.com/schemas/extension.json", + "name": "bitwarden", + "title": "Bitwarden", + "description": "Search, create, and manage Bitwarden vault items via the rbw CLI. Supports self-hosted Bitwarden / Vaultwarden.", + "icon": "icon.png", + "author": "asm0dey", + "license": "MIT", + "categories": ["Productivity", "Security"], + "commands": [ + { "name": "search", "title": "Search Vault", "description": "Search and act on vault items", "mode": "view" }, + { "name": "authenticator", "title": "Authenticator", "description": "Browse TOTP codes", "mode": "view" }, + { "name": "generate-password", "title": "Generate Password", "description": "Open the password generator", "mode": "view" }, + { "name": "generate-password-quick", "title": "Generate Password (Quick)", "description": "Generate and copy a password", "mode": "no-view" }, + { "name": "create-login", "title": "Create Login", "description": "Create a login item", "mode": "view" }, + { "name": "sync-vault", "title": "Sync Vault", "description": "Pull latest vault state from the Bitwarden server", "mode": "no-view" }, + { "name": "lock-vault", "title": "Lock Vault", "description": "Lock the vault", "mode": "no-view" }, + { "name": "logout-vault", "title": "Log Out", "description": "Log out of Bitwarden", "mode": "no-view" } + ], + "preferences": [ + { "name": "clientId", "title": "API Client ID", "type": "password", "required": true, "description": "Bitwarden API client ID (Settings → Security → Keys)" }, + { "name": "clientSecret", "title": "API Client Secret", "type": "password", "required": true, "description": "Bitwarden API client secret" }, + { "name": "cliPath", "title": "Path to rbw CLI", "type": "textfield", "required": false, "default": "rbw", "description": "Leave empty to use $PATH" }, + { "name": "serverUrl", "title": "Self-hosted Server URL", "type": "textfield", "required": false, "default": "", "description": "e.g. https://vault.example.com" }, + { "name": "serverCertsPath", "title": "Self-signed CA Bundle Path", "type": "textfield", "required": false, "default": "", "description": "Path to a PEM file with your CA chain. Injected as SSL_CERT_FILE for rbw." }, + { "name": "syncOnLaunch", "title": "Sync on Launch", "type": "checkbox", "required": false, "default": true, "label": "Sync vault on first launch", "description": "Run `rbw sync` after the first unlock of each session" }, + { "name": "fetchFavicons", "title": "Fetch Favicons", "type": "checkbox", "required": false, "default": true, "label": "Show favicons for login items", "description": "Display favicons next to login items" }, + { "name": "repromptIgnoreDuration", "title": "Reprompt Grace Window", "type": "dropdown", "required": false, "default": "0", "description": "How long to skip the master-password reprompt for sensitive items after a successful prompt", + "data": [ + { "title": "Always reprompt", "value": "0" }, + { "title": "30 seconds", "value": "30000" }, + { "title": "1 minute", "value": "60000" }, + { "title": "5 minutes", "value": "300000" }, + { "title": "15 minutes", "value": "900000" }, + { "title": "Never during session", "value": "never" } + ] + }, + { "name": "windowActionOnCopy", "title": "Window Action on Copy", "type": "dropdown", "required": false, "default": "close", "description": "What Vicinae should do after copying a value to the clipboard", + "data": [ + { "title": "Close window", "value": "close" }, + { "title": "Keep window open", "value": "keepOpen" } + ] + }, + { "name": "shouldCacheVaultItems", "title": "Cache Vault Items", "type": "checkbox", "required": false, "default": true, "label": "Cache non-sensitive fields locally", "description": "Encrypted local cache of names/URIs/folders for fast list rendering. Sensitive fields (passwords, notes, cards, identity) are never cached." } + ], + "dependencies": { + "@vicinae/api": "^0.20.14", + "react": "^19.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.0", + "@types/node": "^22.10.0", + "@types/react": "^19.0.0", + "eslint": "^9.39.0", + "globals": "^17.3.0", + "prettier": "^3.4.0", + "typescript": "^5.7.0", + "typescript-eslint": "^8.21.0", + "vitest": "^3.2.0" + }, + "scripts": { + "build": "vici build", + "dev": "vici develop", + "lint": "eslint .", + "format": "prettier --write .", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + } +} diff --git a/extensions/bitwarden/src/api/errors.ts b/extensions/bitwarden/src/api/errors.ts new file mode 100644 index 00000000..198ef8b3 --- /dev/null +++ b/extensions/bitwarden/src/api/errors.ts @@ -0,0 +1,46 @@ +export class BwError extends Error { + constructor(message: string, public readonly stderr?: string) { + super(message); + this.name = new.target.name; + } +} + +export class BwNotFound extends BwError {} +export class AuthFailed extends BwError {} +export class Locked extends BwError {} +export class NetworkError extends BwError {} +export class ServerCertError extends BwError {} +export class NotLoggedIn extends BwError {} +export class CliInvocationError extends BwError { + constructor(message: string, public readonly exitCode: number, stderr?: string) { + super(message, stderr); + } +} + +export class ItemNotFound extends BwError {} + +export function classifyStderr(stderr: string, exitCode: number): BwError { + const s = stderr.toLowerCase(); + if (s.includes("vault is locked")) return new Locked(stderr.trim(), stderr); + if (s.includes("you are not logged in")) return new NotLoggedIn(stderr.trim(), stderr); + if (s.includes("invalid master password") || s.includes("invalid client_id") || s.includes("invalid client_secret")) + return new AuthFailed(stderr.trim(), stderr); + if (s.includes("self signed certificate") || s.includes("unable to verify the first certificate")) + return new ServerCertError(stderr.trim(), stderr); + if (s.includes("getaddrinfo") || s.includes("econnrefused") || s.includes("network")) + return new NetworkError(stderr.trim(), stderr); + return new CliInvocationError(stderr.trim() || `bw exited with ${exitCode}`, exitCode, stderr); +} + +export function classifyRbwStderr(stderr: string, exitCode: number): BwError { + const s = stderr.toLowerCase(); + if (s.includes("agent is not running") || s.includes("failed to communicate with rbw-agent")) + return new Locked(stderr.trim(), stderr); + if (s.includes("failed to authenticate") || s.includes("invalid email or password")) + return new AuthFailed(stderr.trim(), stderr); + if (s.includes("account is not configured") || s.includes("failed to load db")) + return new NotLoggedIn(stderr.trim(), stderr); + if (s.includes("failed to find entry")) + return new ItemNotFound(stderr.trim(), stderr); + return new CliInvocationError(stderr.trim() || `rbw exited with ${exitCode}`, exitCode, stderr); +} diff --git a/extensions/bitwarden/src/api/rbw-adapter.ts b/extensions/bitwarden/src/api/rbw-adapter.ts new file mode 100644 index 00000000..3f45a8db --- /dev/null +++ b/extensions/bitwarden/src/api/rbw-adapter.ts @@ -0,0 +1,89 @@ +import type { Item, Folder, ItemType, Login } from "../types/bitwarden"; + +export type RbwTypeName = "Login" | "SecureNote" | "Card" | "Identity"; + +export interface RbwListEntry { + id: string; + name: string; + user: string | null; + folder: string | null; + uris: RbwUri[]; + type: RbwTypeName; +} + +type RbwUri = string | { uri: string; match?: number | null }; + +export interface RbwGetEntry { + id: string; + name: string; + folder: string | null; + data: { username: string | null; password: string | null; totp: string | null; uris: RbwUri[] }; + fields: { name: string; value: string | null; type: "text" | "hidden" | "boolean" | "linked" }[]; + notes: string | null; + history: { last_used_date: string; password: string }[]; +} + +function normalizeUris(uris: RbwUri[] | undefined | null): string[] { + if (!uris) return []; + return uris + .map((u) => (typeof u === "string" ? u : u?.uri)) + .filter((u): u is string => typeof u === "string" && u.length > 0); +} + +const TYPE_MAP: Record = { Login: 1, SecureNote: 2, Card: 3, Identity: 4 }; +const FIELD_TYPE_MAP: Record = { + text: 0, hidden: 1, boolean: 2, linked: 3, +}; + +function emptyItem(id: string, name: string, type: ItemType, folderId: string | null): Item { + return { + object: "item", + id, name, type, folderId, + organizationId: null, + reprompt: 0, + notes: null, + favorite: false, + fields: [], + collectionIds: null, + revisionDate: "", + creationDate: "", + deletedDate: null, + }; +} + +function loginFrom(username: string | null, password: string | null, totp: string | null, uris: string[]): Login { + return { + username, password, totp, + uris: uris.map((u) => ({ uri: u, match: null })), + passwordRevisionDate: null, + }; +} + +export function adaptListEntry(e: RbwListEntry): Item { + const item = emptyItem(e.id, e.name, TYPE_MAP[e.type], e.folder ?? null); + if (e.type === "Login") { + item.login = loginFrom(e.user ?? null, null, null, normalizeUris(e.uris)); + } + return item; +} + +export function adaptGetEntry(e: RbwGetEntry, knownType: ItemType = 1): Item { + const item = emptyItem(e.id, e.name, knownType, e.folder ?? null); + if (knownType === 1) { + item.login = loginFrom( + e.data?.username ?? null, + e.data?.password ?? null, + e.data?.totp ?? null, + normalizeUris(e.data?.uris), + ); + } + item.notes = e.notes ?? null; + item.fields = (e.fields ?? []).map((f) => ({ name: f.name, value: f.value, type: FIELD_TYPE_MAP[f.type] ?? 0 })); + return item; +} + +export function deriveFolders(entries: RbwListEntry[]): Folder[] { + const set = new Set(); + for (const e of entries) if (e.folder) set.add(e.folder); + return [...set].sort().map((name): Folder => ({ object: "folder", id: name, name })); +} diff --git a/extensions/bitwarden/src/api/rbw.ts b/extensions/bitwarden/src/api/rbw.ts new file mode 100644 index 00000000..3b005829 --- /dev/null +++ b/extensions/bitwarden/src/api/rbw.ts @@ -0,0 +1,119 @@ +import { spawn, type SpawnOptions } from "node:child_process"; +import { Mutex } from "../utils/mutex"; +import { BwNotFound, CliInvocationError, classifyRbwStderr } from "./errors"; + +export interface RbwCliOptions { + cliPath: string; + serverCertsPath?: string; + extraEnv?: NodeJS.ProcessEnv; +} + +export interface RunOptions { + stdin?: string; + timeoutMs?: number; +} + +const SHARED_MUTEX = new Mutex(); + +export class RbwCli { + constructor(private opts: RbwCliOptions) {} + + /** Returns a new RbwCli instance with extra env merged in. */ + withEnv(extra: NodeJS.ProcessEnv): RbwCli { + return new RbwCli({ ...this.opts, extraEnv: { ...this.opts.extraEnv, ...extra } }); + } + + /** Mutex-serialized text invocation. Use for state-changing rbw commands. */ + async text(args: string[], runOpts: RunOptions = {}): Promise { + const tQueued = Date.now(); + const tag = `[rbw-ext] ${args.join(" ")}`; + return SHARED_MUTEX.run(() => { + const wait = Date.now() - tQueued; + if (wait > 5) console.log(`${tag} mutex-wait=${wait}ms`); + return this.invoke(args, runOpts); + }); + } + + async json(args: string[], runOpts: RunOptions = {}): Promise { + const out = await this.text(args, runOpts); + if (!out.trim()) return undefined; + try { + return JSON.parse(out) as T; + } catch { + throw new CliInvocationError("rbw produced invalid JSON output", 0); + } + } + + /** Read-only: bypasses the mutex. Use for `list`, `get`, `code`, `unlocked`, `config show`. */ + async readText(args: string[], runOpts: RunOptions = {}): Promise { + return this.invoke(args, runOpts); + } + + async readJson(args: string[], runOpts: RunOptions = {}): Promise { + const out = await this.readText(args, runOpts); + if (!out.trim()) return undefined; + try { + return JSON.parse(out) as T; + } catch { + throw new CliInvocationError("rbw produced invalid JSON output", 0); + } + } + + /** Spawn rbw and return raw stdout + exitCode without throwing. Use for `unlocked`-style probes. */ + async tryReadText(args: string[], runOpts: RunOptions = {}): Promise<{stdout: string; exitCode: number}> { + return this.invokeRaw(args, runOpts); + } + + private async invoke(args: string[], runOpts: RunOptions): Promise { + const { stdout, exitCode } = await this.invokeRaw(args, runOpts); + if (exitCode === 0) return stdout; + throw classifyRbwStderr(this.lastStderr, exitCode); + } + + // Holds stderr from the most recent invokeRaw. Mutex serialization makes this safe for `text`. + private lastStderr = ""; + + private invokeRaw(args: string[], runOpts: RunOptions): Promise<{stdout: string; exitCode: number}> { + return new Promise((resolve, reject) => { + const env: NodeJS.ProcessEnv = { ...process.env, ...this.opts.extraEnv }; + if (this.opts.serverCertsPath) env.SSL_CERT_FILE = this.opts.serverCertsPath; + const tag = `[rbw-ext] ${args.join(" ")}`; + const tSpawn = Date.now(); + const spawnOpts: SpawnOptions = { env, stdio: ["pipe", "pipe", "pipe"] }; + const proc = spawn(this.opts.cliPath, args, spawnOpts); + console.log(`${tag} +0ms spawn`); + + let stdout = ""; let stderr = ""; + let firstStdoutAt: number | null = null; + proc.stdout?.on("data", (d) => { + if (firstStdoutAt === null) { + firstStdoutAt = Date.now(); + console.log(`${tag} +${firstStdoutAt - tSpawn}ms first-stdout`); + } + stdout += d.toString(); + }); + proc.stderr?.on("data", (d) => (stderr += d.toString())); + + const timeout = runOpts.timeoutMs ? setTimeout(() => proc.kill("SIGKILL"), runOpts.timeoutMs) : null; + let settled = false; + const settle = (fn: () => void) => { if (settled) return; settled = true; if (timeout) clearTimeout(timeout); fn(); }; + + proc.on("error", (err: NodeJS.ErrnoException) => { + settle(() => err.code === "ENOENT" + ? reject(new BwNotFound(`rbw not found at ${this.opts.cliPath}`)) + : reject(err)); + }); + + proc.on("close", (code) => { + settle(() => { + console.log(`${tag} +${Date.now() - tSpawn}ms close code=${code} stdoutLen=${stdout.length}`); + this.lastStderr = stderr; + resolve({ stdout, exitCode: code ?? -1 }); + }); + }); + + if (runOpts.stdin) { proc.stdin?.write(runOpts.stdin); proc.stdin?.end(); } + else { proc.stdin?.end(); } + }); + } +} diff --git a/extensions/bitwarden/src/api/session-store.ts b/extensions/bitwarden/src/api/session-store.ts new file mode 100644 index 00000000..fd8702aa --- /dev/null +++ b/extensions/bitwarden/src/api/session-store.ts @@ -0,0 +1,97 @@ +import { LocalStorage } from "@vicinae/api"; +import { stripSensitive } from "../utils/cache"; +import type { Folder, Item } from "../types/bitwarden"; + +const KEY_CACHE_ITEMS = "rbw.cache.items"; +const KEY_CACHE_FOLDERS = "rbw.cache.folders"; +const KEY_CACHE_MTIME = "rbw.cache.mtime"; +const KEY_SERVER_URL = "rbw.server-url"; +const KEY_LAST_SYNC = "rbw.last-sync"; +const KEY_REPROMPT = "rbw.reprompt-ts"; +const KEY_TOTP_KNOWN = "rbw.totp.known"; // Set known to have TOTP +const KEY_TOTP_DENY = "rbw.totp.deny"; // Set probed and confirmed no TOTP + +// rbw-agent owns the unlock state. SessionProvider asks rbw whether the agent +// is unlocked instead of consulting persisted token bytes. +export async function saveSession(_token: string): Promise { /* no-op */ } +export async function loadSession(): Promise { return null; } +export async function clearSession(): Promise { /* no-op */ } + +export async function saveCache(items: Item[], folders: Folder[]): Promise { + await LocalStorage.setItem(KEY_CACHE_ITEMS, JSON.stringify(stripSensitive(items))); + await LocalStorage.setItem(KEY_CACHE_FOLDERS, JSON.stringify(folders)); + await LocalStorage.setItem(KEY_CACHE_MTIME, String(Date.now())); +} + +export async function loadCache(): Promise<{ items: Item[]; folders: Folder[]; mtime: number } | null> { + const rawItems = await LocalStorage.getItem(KEY_CACHE_ITEMS); + const rawFolders = await LocalStorage.getItem(KEY_CACHE_FOLDERS); + if (!rawItems || !rawFolders) return null; + const rawMtime = await LocalStorage.getItem(KEY_CACHE_MTIME); + return { + items: JSON.parse(rawItems) as Item[], + folders: JSON.parse(rawFolders) as Folder[], + mtime: rawMtime ? Number(rawMtime) : 0, + }; +} + +export async function clearCache(): Promise { + await LocalStorage.removeItem(KEY_CACHE_ITEMS); + await LocalStorage.removeItem(KEY_CACHE_FOLDERS); + await LocalStorage.removeItem(KEY_CACHE_MTIME); +} + +export async function getLastSync(): Promise { + const raw = await LocalStorage.getItem(KEY_LAST_SYNC); + return raw !== undefined ? Number(raw) : null; +} + +export async function setLastSync(): Promise { + await LocalStorage.setItem(KEY_LAST_SYNC, String(Date.now())); +} + +export async function getLastAppliedServerUrl(): Promise { + const raw = await LocalStorage.getItem(KEY_SERVER_URL); + return raw ?? null; +} + +export async function setLastAppliedServerUrl(url: string): Promise { + await LocalStorage.setItem(KEY_SERVER_URL, url); +} + +export async function setRepromptTimestamp(itemId: string): Promise { + await LocalStorage.setItem(`${KEY_REPROMPT}.${itemId}`, String(Date.now())); +} + +export async function getRepromptTimestamp(itemId: string): Promise { + const raw = await LocalStorage.getItem(`${KEY_REPROMPT}.${itemId}`); + return raw !== undefined ? Number(raw) : null; +} + +export async function clearAllReprompt(): Promise { + const all = await LocalStorage.allItems(); + await Promise.all( + Object.keys(all) + .filter((k) => k.startsWith(`${KEY_REPROMPT}.`)) + .map((k) => LocalStorage.removeItem(k)), + ); +} + +async function readIdSet(key: string): Promise> { + const raw = await LocalStorage.getItem(key); + if (!raw) return new Set(); + try { return new Set(JSON.parse(raw) as string[]); } catch { return new Set(); } +} + +async function writeIdSet(key: string, set: Set): Promise { + await LocalStorage.setItem(key, JSON.stringify([...set])); +} + +export async function loadTotpClassification(): Promise<{ known: Set; deny: Set }> { + const [known, deny] = await Promise.all([readIdSet(KEY_TOTP_KNOWN), readIdSet(KEY_TOTP_DENY)]); + return { known, deny }; +} + +export async function saveTotpClassification(known: Set, deny: Set): Promise { + await Promise.all([writeIdSet(KEY_TOTP_KNOWN, known), writeIdSet(KEY_TOTP_DENY, deny)]); +} diff --git a/extensions/bitwarden/src/api/vault.ts b/extensions/bitwarden/src/api/vault.ts new file mode 100644 index 00000000..5572f66c --- /dev/null +++ b/extensions/bitwarden/src/api/vault.ts @@ -0,0 +1,148 @@ +import type { RbwCli } from "./rbw"; +import type { Folder, Item, ItemType, Status, LockStatus } from "../types/bitwarden"; +import { adaptListEntry, adaptGetEntry, deriveFolders, type RbwListEntry, type RbwGetEntry } from "./rbw-adapter"; +import { resolveEditorShim } from "../utils/prefs"; + +export interface UnlockOptions { + /** When set, password is exposed via env var of this name to the spawned rbw process. Default RBW_PINENTRY_VALUE. */ + passwordEnv?: string; +} + +export type GenerateOpts = + | { mode: "chars"; length: number; symbols: boolean; onlyNumbers?: boolean; nonconfusables?: boolean } + | { mode: "diceware"; words: number; separator?: string; capitalize?: boolean; includeNumber?: boolean }; + +interface RbwConfig { + email: string | null; + base_url?: string | null; +} + +export class Vault { + constructor(private cli: RbwCli) {} + + /** Sentinel parity with bw's BW_SESSION token. The agent is the source of truth. */ + withSession(_session: string): Vault { return this; } + + async status(): Promise { + const cfg = await this.cli.readJson(["config", "show"]); + if (!cfg) return undefined; + const unlocked = (await this.cli.tryReadText(["unlocked"])).exitCode === 0; + const status: LockStatus = !cfg.email + ? "unauthenticated" + : unlocked ? "unlocked" : "locked"; + return { + status, + serverUrl: cfg.base_url ?? null, + userEmail: cfg.email ?? null, + userId: null, + lastSync: null, + }; + } + + async sync(): Promise { await this.cli.text(["sync"]); } + + async configServer(url: string): Promise { await this.cli.text(["config", "set", "base_url", url]); } + async configEmail(email: string): Promise { await this.cli.text(["config", "set", "email", email]); } + + /** + * rbw register reads client_id and client_secret interactively. + * We feed them via stdin (one per line). Caller must pre-set the email via configEmail. + */ + async register(clientId: string, clientSecret: string): Promise { + await this.cli.text(["register"], { stdin: `${clientId}\n${clientSecret}\n` }); + } + + /** + * rbw login reads master password via the configured pinentry. Caller must: + * 1. set rbw config pinentry to the shim + * 2. provide RBW_PINENTRY_VALUE on the cli env + */ + async login(): Promise { await this.cli.text(["login"]); } + + /** + * Unlock through the pinentry shim. Returns the sentinel "rbw-agent". + * Caller is expected to have routed pinentry to the shim and set RBW_PINENTRY_VALUE + * on the underlying RbwCli (via withEnv). + */ + async unlock(_masterPassword: string, _opts: UnlockOptions = {}): Promise { + await this.cli.text(["unlock"]); + return "rbw-agent"; + } + + async lock(): Promise { await this.cli.text(["lock"]); } + + async logout(): Promise { + try { await this.cli.text(["stop-agent"]); } catch { /* best effort */ } + await this.cli.text(["purge"]); + } + + async listItems(): Promise { + const raw = await this.cli.readJson(["list", "--raw"]); + return raw?.map(adaptListEntry); + } + + async listFolders(): Promise { + const raw = await this.cli.readJson(["list", "--raw"]); + return raw ? deriveFolders(raw) : undefined; + } + + async getItem(id: string, knownType?: ItemType): Promise { + const raw = await this.cli.readJson(["get", "--raw", id]); + return raw ? adaptGetEntry(raw, knownType) : undefined; + } + + async getTotp(id: string): Promise { + return (await this.cli.readText(["code", id])).trim(); + } + + /** + * Create a new login. Routes rbw add through the embedded editor shim: + * EDITOR points at the shim; RBW_EDITOR_PAYLOAD carries the buffer rbw + * receives — first line is the password, the rest is notes (rbw's + * convention). TOTP is stored as a `totp:` line in notes per rbw docs. + */ + async createItem(item: { name: string; username?: string; password?: string; uri?: string; folderId?: string | null; notes?: string; totp?: string }): Promise<{ name: string }> { + const args = ["add"]; + if (item.uri) args.push("--uri", item.uri); + if (item.folderId) args.push("--folder", item.folderId); + args.push(item.name); + if (item.username) args.push(item.username); + const noteLines: string[] = []; + if (item.totp) noteLines.push(`totp: ${item.totp}`); + if (item.notes) noteLines.push(item.notes); + const payload = noteLines.length > 0 + ? `${item.password ?? ""}\n\n${noteLines.join("\n")}` + : `${item.password ?? ""}\n`; + const env: NodeJS.ProcessEnv = { EDITOR: resolveEditorShim(), RBW_EDITOR_PAYLOAD: payload }; + await this.cli.withEnv(env).text(args); + return { name: item.name }; + } + + async deleteItem(id: string): Promise { await this.cli.text(["remove", id]); } + + async generatePassword(opts: GenerateOpts): Promise { + const args: string[] = ["generate"]; + if (opts.mode === "diceware") { + args.push(String(opts.words), "--diceware"); + const raw = (await this.cli.text(args)).trim(); + // rbw delimits diceware words with whitespace; tolerate dashes too in + // case the format ever changes. + let words = raw.split(/[\s-]+/).filter((w) => w.length > 0); + if (opts.capitalize) words = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)); + if (opts.includeNumber && words.length > 0) { + // Append a single random digit to a randomly-chosen word so the + // passphrase satisfies sites that demand a number, mirroring bw's + // behaviour. + const idx = Math.floor(Math.random() * words.length); + const digit = String(Math.floor(Math.random() * 10)); + words[idx] = words[idx] + digit; + } + return words.join(opts.separator ?? "-"); + } + args.push(String(opts.length)); + if (!opts.symbols) args.push("--no-symbols"); + if (opts.onlyNumbers) args.push("--only-numbers"); + if (opts.nonconfusables) args.push("--nonconfusables"); + return (await this.cli.text(args)).trim(); + } +} diff --git a/extensions/bitwarden/src/authenticator.tsx b/extensions/bitwarden/src/authenticator.tsx new file mode 100644 index 00000000..c10df63d --- /dev/null +++ b/extensions/bitwarden/src/authenticator.tsx @@ -0,0 +1,233 @@ +import { List, ActionPanel, Action, Clipboard, Icon, showToast, Toast } from "@vicinae/api"; +import { useEffect, useState } from "react"; +import { SessionProvider, useSession } from "./context/session-provider"; +import { VaultProvider, useVault } from "./context/vault-provider"; +import { ApiKeyLoginForm } from "./components/api-key-login-form"; +import { UnlockForm } from "./components/unlock-form"; +import type { Item } from "./types/bitwarden"; +import { Vault } from "./api/vault"; +import { loadTotpClassification, saveTotpClassification } from "./api/session-store"; + +const PROBE_CONCURRENCY = 8; + +async function runWithLimit(items: T[], limit: number, fn: (t: T) => Promise): Promise { + const results = new Array(items.length); + let cursor = 0; + const worker = async () => { + for (;;) { + const idx = cursor++; + if (idx >= items.length) return; + results[idx] = await fn(items[idx]!); + } + }; + await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker)); + return results; +} + +const TOTP_PERIOD = 30; + +function formatCode(c: string): string { + if (c.length === 6) return `${c.slice(0, 3)} ${c.slice(3)}`; + if (c.length === 8) return `${c.slice(0, 4)} ${c.slice(4)}`; + return c; +} + +function useSharedTotpClock(): { window: number; remaining: number } { + // One interval per renderer instead of one-per-item. All TotpItems read + // the same window/remaining values so countdowns stay in lockstep. + const [now, setNow] = useState(() => Math.floor(Date.now() / 1000)); + useEffect(() => { + const id = setInterval(() => setNow(Math.floor(Date.now() / 1000)), 1000); + return () => clearInterval(id); + }, []); + return { + window: Math.floor(now / TOTP_PERIOD), + remaining: TOTP_PERIOD - (now % TOTP_PERIOD), + }; +} + +function TotpItem({ item, vault, window, remaining }: { item: Item; vault: Vault; window: number; remaining: number }) { + const [code, setCode] = useState("…"); + const [lastFetched, setLastFetched] = useState(-1); + + useEffect(() => { + if (window === lastFetched) return; + let cancelled = false; + void (async () => { + try { + const c = await vault.getTotp(item.id); + if (cancelled) return; + if (c) setCode(c); + setLastFetched(window); + } catch { /* keep prior value */ } + })(); + return () => { cancelled = true; }; + }, [item.id, vault, window, lastFetched]); + + return ( + + { + try { + const c = code === "…" ? await vault.getTotp(item.id) : code; + if (!c) { + await showToast({ style: Toast.Style.Failure, title: "No TOTP code for this item" }); + return; + } + await Clipboard.copy(c, { concealed: true }); + await showToast({ style: Toast.Style.Success, title: "Copied", message: c }); + } catch (e) { + await showToast({ style: Toast.Style.Failure, title: "Fetch failed", message: String(e) }); + } + }} + /> + + } + /> + ); +} + +// Module-level guard. With React effects firing twice (cache hydrate, then +// refresh) AuthList would otherwise launch two parallel probes that race +// each other in LocalStorage. +let probeInFlight = false; +let lastItemSignature = ""; + +function itemSignature(items: Item[]): string { + const ids = items.filter((i) => i.type === 1).map((i) => i.id).sort(); + if (ids.length === 0) return "empty"; + return `${ids.length}:${ids[0]}:${ids[ids.length - 1]}`; +} + +function AuthList() { + const { state } = useSession(); + const { items, isLoading } = useVault(); + const [totpItems, setTotpItems] = useState([]); + const [probing, setProbing] = useState(false); + + useEffect(() => { + if (state.kind !== "unlocked") return; + const sig = itemSignature(items); + if (sig === lastItemSignature) return; + if (probeInFlight) return; + lastItemSignature = sig; + probeInFlight = true; + const vault = state.vault; + let cancelled = false; + + void (async () => { + const { known, deny } = await loadTotpClassification(); + if (cancelled) return; + console.log(`[rbw-ext] auth-cache loaded known=${known.size} deny=${deny.size}`); + + const candidates = items.filter((i) => i.type === 1); + // Items list is empty on the very first render (VaultProvider hasn't + // hydrated yet). Don't touch the persisted classification in that + // window; the next render will run with real data. + if (candidates.length === 0) { + console.log(`[rbw-ext] auth-probe skip — items not hydrated yet`); + probeInFlight = false; + return; + } + const candidateIds = new Set(candidates.map((i) => i.id)); + const cachedHits = candidates.filter((i) => known.has(i.id)); + setTotpItems(cachedHits); + + const unclassified = candidates.filter((i) => !known.has(i.id) && !deny.has(i.id)); + if (unclassified.length === 0) { + const trimmedKnown = new Set([...known].filter((id) => candidateIds.has(id))); + const trimmedDeny = new Set([...deny].filter((id) => candidateIds.has(id))); + if (trimmedKnown.size !== known.size || trimmedDeny.size !== deny.size) { + await saveTotpClassification(trimmedKnown, trimmedDeny); + } + console.log(`[rbw-ext] auth-probe skip — all ${candidates.length} candidates already classified`); + probeInFlight = false; + return; + } + + setProbing(true); + const tStart = Date.now(); + console.log(`[rbw-ext] auth-probe start unclassified=${unclassified.length} cached-hits=${cachedHits.length}`); + + const newKnown = new Set(known); + const newDeny = new Set(deny); + let pendingHits: Item[] = []; + let lastFlush = Date.now(); + let dirtySinceSave = 0; + + const persist = async () => { + if (dirtySinceSave === 0) return; + const trimmedKnown = new Set([...newKnown].filter((id) => candidateIds.has(id))); + const trimmedDeny = new Set([...newDeny].filter((id) => candidateIds.has(id))); + await saveTotpClassification(trimmedKnown, trimmedDeny); + dirtySinceSave = 0; + }; + + await runWithLimit(unclassified, PROBE_CONCURRENCY, async (i) => { + try { + const code = await vault.getTotp(i.id); + if (code) { + newKnown.add(i.id); + pendingHits.push(i); + } else { + newDeny.add(i.id); + } + } catch { + newDeny.add(i.id); + } + dirtySinceSave += 1; + // Flush UI + persisted classification incrementally so a closed + // window doesn't lose progress for the next launch. + if (pendingHits.length >= 8 || Date.now() - lastFlush > 500) { + if (cancelled) return; + const batch = pendingHits; + pendingHits = []; + lastFlush = Date.now(); + if (batch.length > 0) setTotpItems((prev) => [...prev, ...batch]); + } + if (dirtySinceSave >= 50) await persist(); + }); + + if (!cancelled && pendingHits.length > 0) setTotpItems((prev) => [...prev, ...pendingHits]); + await persist(); + console.log(`[rbw-ext] auth-probe total=${Date.now() - tStart}ms hits=${newKnown.size - known.size} misses=${newDeny.size - deny.size}`); + if (!cancelled) setProbing(false); + probeInFlight = false; + })(); + return () => { + cancelled = true; + probeInFlight = false; + }; + }, [items, state]); + + const clock = useSharedTotpClock(); + + if (state.kind !== "unlocked") return ; + return ( + + {totpItems.map((item) => ( + + ))} + + ); +} + +function Inner() { + const { state, invalidateSession } = useSession(); + if (state.kind === "loading") return ; + if (state.kind === "needs-login") return ; + if (state.kind === "needs-unlock") return ; + if (state.kind === "needs-cli") return ; + return void invalidateSession()}>; +} + +export default function Authenticator() { + return ; +} diff --git a/extensions/bitwarden/src/components/api-key-login-form.tsx b/extensions/bitwarden/src/components/api-key-login-form.tsx new file mode 100644 index 00000000..36180c87 --- /dev/null +++ b/extensions/bitwarden/src/components/api-key-login-form.tsx @@ -0,0 +1,42 @@ +import { Form, ActionPanel, Action } from "@vicinae/api"; +import { useState } from "react"; +import { useSession } from "../context/session-provider"; +import { getPrefs } from "../utils/prefs"; + +export function ApiKeyLoginForm() { + const prefs = getPrefs(); + const { loginApiKey } = useSession(); + const [submitting, setSubmitting] = useState(false); + + return ( +
+ { + setSubmitting(true); + try { + await loginApiKey( + String(values["email"] ?? ""), + String(values["clientId"] ?? ""), + String(values["clientSecret"] ?? ""), + String(values["master"] ?? ""), + ); + } catch { /* toast handled in provider */ } + finally { setSubmitting(false); } + }} + /> + + } + > + + + + + + + ); +} diff --git a/extensions/bitwarden/src/components/item-detail.tsx b/extensions/bitwarden/src/components/item-detail.tsx new file mode 100644 index 00000000..80370f35 --- /dev/null +++ b/extensions/bitwarden/src/components/item-detail.tsx @@ -0,0 +1,138 @@ +import { Detail, ActionPanel, Action, Clipboard, Icon, showToast, Toast } from "@vicinae/api"; +import { useEffect, useState } from "react"; +import type { Item } from "../types/bitwarden"; +import type { Vault } from "../api/vault"; + +export function ItemDetail({ item, vault }: { item: Item; vault: Vault }) { + const [full, setFull] = useState(item); + const [totp, setTotp] = useState(null); + const [revealed, setRevealed] = useState(false); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + void (async () => { + try { + const fresh = await vault.getItem(item.id); + if (!cancelled && fresh) setFull(fresh); + } catch { /* keep stub */ } + try { + const code = await vault.getTotp(item.id); + if (!cancelled && code) setTotp(code); + } catch { /* no totp */ } + if (!cancelled) setLoading(false); + })(); + return () => { cancelled = true; }; + }, [item.id, vault]); + + const md = `# ${full.name}${loading ? "\n\n_Loading…_" : ""}${full.notes ? `\n\n## Notes\n\n${full.notes}` : ""}`; + + return ( + + {full.login?.username && } + {full.login?.password && ( + + )} + {totp && } + {full.login?.uris?.length ? : null} + {full.login?.uris?.map((u, i) => ( + + ))} + {full.card && ( + <> + + {full.card.cardholderName && } + {full.card.brand && } + {full.card.number && } + {(full.card.expMonth || full.card.expYear) && ( + + )} + {full.card.code && } + + )} + {full.identity && ( + <> + + {full.identity.firstName && } + {full.identity.lastName && } + {full.identity.email && } + {full.identity.phone && } + + )} + {full.fields && full.fields.length > 0 && ( + <> + + {full.fields.map((f, i) => ( + + ))} + + )} + + } + actions={ + + {full.login?.password && ( + { + await Clipboard.copy(full.login!.password!, { concealed: true }); + await showToast({ style: Toast.Style.Success, title: "Password copied" }); + }} + /> + )} + {totp && ( + { + await Clipboard.copy(totp, { concealed: true }); + await showToast({ style: Toast.Style.Success, title: "TOTP copied", message: totp }); + }} + /> + )} + {full.login?.username && ( + { + await Clipboard.copy(full.login!.username!); + await showToast({ style: Toast.Style.Success, title: "Username copied" }); + }} + /> + )} + {full.login?.password && ( + setRevealed((r) => !r)} + /> + )} + {full.login?.uris?.[0]?.uri && ( + + )} + + } + /> + ); +} diff --git a/extensions/bitwarden/src/components/item-list.tsx b/extensions/bitwarden/src/components/item-list.tsx new file mode 100644 index 00000000..5c0f0b25 --- /dev/null +++ b/extensions/bitwarden/src/components/item-list.tsx @@ -0,0 +1,134 @@ +import { List, ActionPanel, Action, Clipboard, Icon, useNavigation, showToast, Toast } from "@vicinae/api"; +import type { Item } from "../types/bitwarden"; +import { ItemDetail } from "./item-detail"; +import { RepromptForm } from "./reprompt-form"; +import { useVault } from "../context/vault-provider"; +import { useSession } from "../context/session-provider"; +import { getPrefs, repromptGraceMs } from "../utils/prefs"; +import { getRepromptTimestamp, setRepromptTimestamp } from "../api/session-store"; + +// Icon.Document and Icon.Globe do not exist in @vicinae/api — using Icon.BlankDocument and Icon.Globe01 as fallbacks. +const TYPE_ICON: Record = { + 1: Icon.Key, + 2: Icon.BlankDocument, + 3: Icon.CreditCard, + 4: Icon.Person, +}; + +export function ItemList() { + const { items, folders, isLoading, refresh } = useVault(); + const folderName = (id: string | null) => folders.find((f) => f.id === id)?.name ?? "No Folder"; + const grouped = group(items, (i) => folderName(i.folderId)); + + return ( + + {Object.entries(grouped).map(([folder, list]) => ( + + {list.map((item) => ( + u.uri) ?? []} + icon={TYPE_ICON[item.type] ?? Icon.BlankDocument} + actions={} + /> + ))} + + ))} + + ); +} + +function ItemActions({ item, onChanged }: { item: Item; onChanged: () => Promise }) { + const { push } = useNavigation(); + const { state } = useSession(); + if (state.kind !== "unlocked") return null; + const vault = state.vault; + const prefs = getPrefs(); + + const guarded = async (action: () => Promise) => { + if (item.reprompt === 1) { + const last = await getRepromptTimestamp(item.id); + const grace = repromptGraceMs(prefs); + const fresh = grace !== "never" && typeof grace === "number" && last !== null && Date.now() - last < grace; + const sessionGrace = grace === "never" && last !== null; + if (!fresh && !sessionGrace) { + return push( + { + await setRepromptTimestamp(item.id); + await action(); + }} + onCancel={() => undefined} + />, + ); + } + } + await action(); + }; + + const copyPassword = () => + guarded(async () => { + const fresh = await vault.getItem(item.id); + if (!fresh?.login?.password) { + await showToast({ style: Toast.Style.Failure, title: "No password" }); + return; + } + await Clipboard.copy(fresh.login.password, { concealed: true }); + }); + + const copyTotp = () => + guarded(async () => { + const code = await vault.getTotp(item.id); + if (!code) { + await showToast({ style: Toast.Style.Failure, title: "No TOTP" }); + return; + } + await Clipboard.copy(code, { concealed: true }); + }); + + const pastePassword = () => + guarded(async () => { + const fresh = await vault.getItem(item.id); + if (!fresh?.login?.password) return; + await Clipboard.paste(fresh.login.password); + }); + + const uri = item.login?.uris?.[0]?.uri; + + const isLogin = item.type === 1; + + return ( + + push()} /> + {isLogin && } + {isLogin && } + {isLogin && } + {item.login?.username && ( + { + await Clipboard.copy(item.login!.username!); + await showToast({ style: Toast.Style.Success, title: "Username copied" }); + }} + /> + )} + {uri && } + + + ); +} + +function group(arr: T[], keyer: (t: T) => string): Record { + const out: Record = {}; + for (const t of arr) (out[keyer(t)] ??= []).push(t); + return out; +} diff --git a/extensions/bitwarden/src/components/reprompt-form.tsx b/extensions/bitwarden/src/components/reprompt-form.tsx new file mode 100644 index 00000000..2da5b9ae --- /dev/null +++ b/extensions/bitwarden/src/components/reprompt-form.tsx @@ -0,0 +1,55 @@ +import { Form, ActionPanel, Action } from "@vicinae/api"; +import { useState } from "react"; +import { Vault } from "../api/vault"; +import { RbwCli } from "../api/rbw"; +import { getPrefs, resolveCliPath, resolvePinentryShim } from "../utils/prefs"; + +export function RepromptForm({ onConfirmed, onCancel }: { onConfirmed: () => void; onCancel: () => void }) { + const prefs = getPrefs(); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(); + + return ( +
+ { + setSubmitting(true); setError(undefined); + try { + const master = String(values["master"] ?? ""); + const base = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const cliWithMP = base.withEnv({ RBW_PINENTRY_VALUE: master }); + const pinShim = resolvePinentryShim(); + let prior = "pinentry"; + try { + const cfg = await base.readJson<{ pinentry?: string | null }>(["config", "show"]); + const v = cfg?.pinentry?.trim(); + if (v && v.length > 0) prior = v; + } catch { /* fallback to "pinentry" */ } + await cliWithMP.text(["config", "set", "pinentry", pinShim]); + try { + await new Vault(cliWithMP).unlock(master); + onConfirmed(); + } finally { + try { await cliWithMP.text(["config", "set", "pinentry", prior]); } catch { /* best effort */ } + } + } catch { + setError("Invalid master password"); + } finally { + setSubmitting(false); + } + }} + /> + + + } + > + + + + ); +} diff --git a/extensions/bitwarden/src/components/unlock-form.tsx b/extensions/bitwarden/src/components/unlock-form.tsx new file mode 100644 index 00000000..9e569e16 --- /dev/null +++ b/extensions/bitwarden/src/components/unlock-form.tsx @@ -0,0 +1,29 @@ +import { Form, ActionPanel, Action } from "@vicinae/api"; +import { useState } from "react"; +import { useSession } from "../context/session-provider"; + +export function UnlockForm() { + const { unlock } = useSession(); + const [submitting, setSubmitting] = useState(false); + return ( +
+ { + setSubmitting(true); + try { await unlock(String(values["master"] ?? "")); } + catch { /* toast handled in provider */ } + finally { setSubmitting(false); } + }} + /> + + } + > + + + ); +} diff --git a/extensions/bitwarden/src/context/session-provider.tsx b/extensions/bitwarden/src/context/session-provider.tsx new file mode 100644 index 00000000..c1e8dab3 --- /dev/null +++ b/extensions/bitwarden/src/context/session-provider.tsx @@ -0,0 +1,169 @@ +import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; +import { showToast, Toast } from "@vicinae/api"; +import { RbwCli } from "../api/rbw"; +import { Vault } from "../api/vault"; +import { getPrefs, resolveCliPath, resolvePinentryShim } from "../utils/prefs"; +import { + getLastAppliedServerUrl, + setLastAppliedServerUrl, + getLastSync, + setLastSync, +} from "../api/session-store"; +import { Locked, NotLoggedIn, AuthFailed, BwNotFound } from "../api/errors"; + +type AuthState = + | { kind: "loading" } + | { kind: "needs-cli" } + | { kind: "needs-login" } + | { kind: "needs-unlock" } + | { kind: "unlocked"; vault: Vault; session: string }; + +interface SessionContextValue { + state: AuthState; + unlock: (masterPassword: string) => Promise; + lock: () => Promise; + logout: () => Promise; + loginApiKey: (email: string, clientId: string, clientSecret: string, masterPassword: string) => Promise; + invalidateSession: () => Promise; +} + +const Ctx = createContext(null); + +const SENTINEL_SESSION = "rbw-agent"; + +interface RbwConfigSnapshot { pinentry?: string | null } + +async function ensurePinentryShim(cli: RbwCli, shimPath: string): Promise { + // rbw kills the agent on any `rbw config set`, which would discard a + // freshly-unlocked vault key. Only write the config when the current + // value actually differs from our shim path. + let current: string | null = null; + try { + const cfg = await cli.readJson(["config", "show"]); + current = cfg?.pinentry ?? null; + } catch { /* fall through and set */ } + if (current === shimPath) return; + await cli.text(["config", "set", "pinentry", shimPath]); +} + +export function SessionProvider({ children }: { children: ReactNode }) { + const prefs = getPrefs(); + const [state, setState] = useState({ kind: "loading" }); + + const baseCli = useMemo( + () => new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }), + [prefs.cliPath, prefs.serverCertsPath], + ); + + const refresh = useCallback(async () => { + try { + if (prefs.serverUrl) { + const last = await getLastAppliedServerUrl(); + if (last !== prefs.serverUrl) { + try { + await new Vault(baseCli).configServer(prefs.serverUrl); + await setLastAppliedServerUrl(prefs.serverUrl); + } catch { /* idempotent */ } + } + } + const status = await new Vault(baseCli).status(); + if (!status || status.status === "unauthenticated") setState({ kind: "needs-login" }); + else if (status.status === "locked") setState({ kind: "needs-unlock" }); + else setState({ kind: "unlocked", vault: new Vault(baseCli), session: SENTINEL_SESSION }); + } catch (e) { + if (e instanceof BwNotFound) setState({ kind: "needs-cli" }); + else setState({ kind: "needs-login" }); + } + }, [baseCli, prefs.serverUrl]); + + const invalidateSession = useCallback(async () => { + setState({ kind: "needs-unlock" }); + }, []); + + useEffect(() => { void refresh(); }, [refresh]); + + const withPinentry = useCallback(async (masterPassword: string, fn: (cli: RbwCli) => Promise): Promise => { + const pinShim = resolvePinentryShim(); + const cliWithMP = baseCli.withEnv({ RBW_PINENTRY_VALUE: masterPassword }); + // Make the shim the persistent pinentry. `rbw config set` kills the + // agent on every write, so we only write when the value actually + // differs — that way unlocks across one Vicinae session don't + // re-lock the vault. The shim is harmless in foreign processes + // (RBW_PINENTRY_VALUE unset → empty password → unlock fails + // cleanly), and users who want their original pinentry back can + // run `rbw config set pinentry ` from a terminal. + await ensurePinentryShim(baseCli, pinShim); + return fn(cliWithMP); + }, [baseCli]); + + const loginApiKey = useCallback(async (email: string, clientId: string, clientSecret: string, masterPassword: string) => { + try { + await new Vault(baseCli).configEmail(email); + if (prefs.serverUrl) { + try { + await new Vault(baseCli).configServer(prefs.serverUrl); + await setLastAppliedServerUrl(prefs.serverUrl); + } catch { /* idempotent */ } + } + await withPinentry(masterPassword, async (cliWithMP) => { + const v = new Vault(cliWithMP); + await v.register(clientId, clientSecret); + await v.login(); + }); + setState({ kind: "unlocked", vault: new Vault(baseCli), session: SENTINEL_SESSION }); + } catch (e) { + if (e instanceof AuthFailed) { + await showToast({ style: Toast.Style.Failure, title: "Invalid credentials" }); + } else { + await showToast({ style: Toast.Style.Failure, title: "Login failed", message: String(e) }); + } + throw e; + } + }, [baseCli, prefs.serverUrl, withPinentry]); + + const unlock = useCallback(async (masterPassword: string) => { + try { + await withPinentry(masterPassword, async (cliWithMP) => { + await new Vault(cliWithMP).unlock(masterPassword); + }); + const sessionedVault = new Vault(baseCli); + if (prefs.syncOnLaunch) { + const last = await getLastSync(); + const SYNC_TTL_MS = 12 * 60 * 60 * 1000; + if (!last || Date.now() - last > SYNC_TTL_MS) { + try { await sessionedVault.sync(); await setLastSync(); } catch { /* best effort */ } + } + } + setState({ kind: "unlocked", vault: sessionedVault, session: SENTINEL_SESSION }); + } catch (e) { + if (e instanceof AuthFailed || e instanceof Locked) { + await showToast({ style: Toast.Style.Failure, title: "Invalid master password" }); + } else if (e instanceof NotLoggedIn) { + setState({ kind: "needs-login" }); + } else { + await showToast({ style: Toast.Style.Failure, title: "Unlock failed", message: String(e) }); + } + throw e; + } + }, [baseCli, prefs.syncOnLaunch, withPinentry]); + + const lock = useCallback(async () => { + try { await new Vault(baseCli).lock(); } catch { /* best effort */ } + setState({ kind: "needs-unlock" }); + }, [baseCli]); + + const logout = useCallback(async () => { + try { await new Vault(baseCli).logout(); } catch { /* best effort */ } + setState({ kind: "needs-login" }); + }, [baseCli]); + + return ( + {children} + ); +} + +export function useSession(): SessionContextValue { + const c = useContext(Ctx); + if (!c) throw new Error("useSession outside SessionProvider"); + return c; +} diff --git a/extensions/bitwarden/src/context/vault-provider.tsx b/extensions/bitwarden/src/context/vault-provider.tsx new file mode 100644 index 00000000..448444da --- /dev/null +++ b/extensions/bitwarden/src/context/vault-provider.tsx @@ -0,0 +1,72 @@ +import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; +import { Vault } from "../api/vault"; +import { loadCache, saveCache } from "../api/session-store"; +import { Locked, AuthFailed, NotLoggedIn } from "../api/errors"; +import type { Item, Folder } from "../types/bitwarden"; + +interface VaultContextValue { + items: Item[]; + folders: Folder[]; + isLoading: boolean; + refresh: () => Promise; +} + +const Ctx = createContext(null); + +export function VaultProvider({ + vault, + onSessionInvalid, + children, +}: { + vault: Vault; + onSessionInvalid?: () => void; + children: ReactNode; +}) { + const [items, setItems] = useState([]); + const [folders, setFolders] = useState([]); + const [isLoading, setLoading] = useState(true); + + const refresh = async () => { + try { + const [is, fs] = await Promise.all([vault.listItems(), vault.listFolders()]); + const items = is ?? []; + const folders = fs ?? []; + setItems(items); + setFolders(folders); + void saveCache(items, folders); + } catch (e) { + if (e instanceof Locked || e instanceof AuthFailed || e instanceof NotLoggedIn) { + onSessionInvalid?.(); + return; + } + // network or other transient errors — keep cached data, don't disturb UI + } finally { + setLoading(false); + } + }; + + useEffect(() => { + let cancel = false; + void (async () => { + const cached = await loadCache(); + if (cancel) return; + if (cached) { + setItems(cached.items); + setFolders(cached.folders); + setLoading(false); + } + const FRESH_MS = 10 * 60 * 1000; + if (cached && Date.now() - cached.mtime < FRESH_MS) return; + await refresh(); + })(); + return () => { cancel = true; }; + }, [vault]); + + return {children}; +} + +export function useVault(): VaultContextValue { + const c = useContext(Ctx); + if (!c) throw new Error("useVault outside VaultProvider"); + return c; +} diff --git a/extensions/bitwarden/src/create-login.tsx b/extensions/bitwarden/src/create-login.tsx new file mode 100644 index 00000000..6e6859c1 --- /dev/null +++ b/extensions/bitwarden/src/create-login.tsx @@ -0,0 +1,68 @@ +import { Form, ActionPanel, Action, showToast, Toast, popToRoot } from "@vicinae/api"; +import { SessionProvider, useSession } from "./context/session-provider"; +import { VaultProvider, useVault } from "./context/vault-provider"; +import { ApiKeyLoginForm } from "./components/api-key-login-form"; +import { UnlockForm } from "./components/unlock-form"; + +function Inner() { + const { state, invalidateSession } = useSession(); + if (state.kind === "needs-login") return ; + if (state.kind === "needs-unlock") return ; + if (state.kind !== "unlocked") return
; + return void invalidateSession()}>; +} + +function InnerForm() { + const { state } = useSession(); + const { folders } = useVault(); + if (state.kind !== "unlocked") return ; + const vault = state.vault; + + return ( + + { + const name = String(v["name"] ?? "").trim(); + if (!name) return; + const folderId = String(v["folderId"] ?? "_none_"); + try { + await vault.createItem({ + name, + username: String(v["username"] ?? "") || undefined, + password: String(v["password"] ?? "") || undefined, + uri: String(v["uri"] ?? "") || undefined, + folderId: folderId === "_none_" ? undefined : folderId, + notes: String(v["notes"] ?? "") || undefined, + totp: String(v["totp"] ?? "") || undefined, + }); + await showToast({ style: Toast.Style.Success, title: "Login created" }); + await popToRoot(); + } catch (e) { + await showToast({ style: Toast.Style.Failure, title: "Create failed", message: String(e) }); + } + }} + /> + + } + > + + + + + + + + {folders.map((f) => f.id ? : null)} + + + + ); +} + +export default function CreateLogin() { + return ; +} diff --git a/extensions/bitwarden/src/generate-password-quick.tsx b/extensions/bitwarden/src/generate-password-quick.tsx new file mode 100644 index 00000000..05748538 --- /dev/null +++ b/extensions/bitwarden/src/generate-password-quick.tsx @@ -0,0 +1,17 @@ +import { Clipboard, showToast, Toast } from "@vicinae/api"; +import { RbwCli } from "./api/rbw"; +import { Vault } from "./api/vault"; +import { getPrefs, resolveCliPath } from "./utils/prefs"; + +export default async function () { + const prefs = getPrefs(); + const cli = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const vault = new Vault(cli); + try { + const pwd = await vault.generatePassword({ mode: "chars", length: 24, symbols: true }); + await Clipboard.copy(pwd, { concealed: true }); + await showToast({ style: Toast.Style.Success, title: "Password copied", message: pwd }); + } catch (e) { + await showToast({ style: Toast.Style.Failure, title: "Generate failed", message: String(e) }); + } +} diff --git a/extensions/bitwarden/src/generate-password.tsx b/extensions/bitwarden/src/generate-password.tsx new file mode 100644 index 00000000..390c7521 --- /dev/null +++ b/extensions/bitwarden/src/generate-password.tsx @@ -0,0 +1,138 @@ +import { Form, ActionPanel, Action, Clipboard, LocalStorage, showToast, Toast } from "@vicinae/api"; +import { useEffect, useState } from "react"; +import { RbwCli } from "./api/rbw"; +import { Vault } from "./api/vault"; +import { getPrefs, resolveCliPath } from "./utils/prefs"; + +type Mode = "chars" | "diceware"; + +interface Settings { + mode: Mode; + length: string; + symbols: boolean; + onlyNumbers: boolean; + nonconfusables: boolean; + words: string; + separator: string; + capitalize: boolean; + includeNumber: boolean; +} + +const DEFAULTS: Settings = { + mode: "chars", + length: "20", + symbols: true, + onlyNumbers: false, + nonconfusables: false, + words: "5", + separator: "-", + capitalize: false, + includeNumber: false, +}; + +const KEY_SETTINGS = "bw.generate-password.settings"; + +async function loadSettings(): Promise { + const raw = await LocalStorage.getItem(KEY_SETTINGS); + if (!raw) return DEFAULTS; + try { return { ...DEFAULTS, ...(JSON.parse(raw) as Partial) }; } + catch { return DEFAULTS; } +} + +async function saveSettings(s: Settings): Promise { + await LocalStorage.setItem(KEY_SETTINGS, JSON.stringify(s)); +} + +export default function GeneratePassword() { + const [settings, setSettings] = useState(DEFAULTS); + const [hydrated, setHydrated] = useState(false); + const [pwd, setPwd] = useState(""); + + useEffect(() => { + void (async () => { + setSettings(await loadSettings()); + setHydrated(true); + })(); + }, []); + + const update = (key: K, value: Settings[K]) => { + setSettings((s) => { + const next = { ...s, [key]: value }; + void saveSettings(next); + return next; + }); + }; + + const generate = async () => { + const prefs = getPrefs(); + const cli = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const vault = new Vault(cli); + const out = await vault.generatePassword( + settings.mode === "diceware" + ? { + mode: "diceware", + words: Math.max(1, Number(settings.words)), + separator: settings.separator, + capitalize: settings.capitalize, + includeNumber: settings.includeNumber, + } + : { + mode: "chars", + length: Math.max(4, Number(settings.length)), + symbols: settings.symbols, + onlyNumbers: settings.onlyNumbers, + nonconfusables: settings.nonconfusables, + }, + ); + setPwd(out); + await Clipboard.copy(out, { concealed: true }); + await showToast({ style: Toast.Style.Success, title: "Password copied", message: out }); + }; + + if (!hydrated) return
; + + return ( + + { + try { await generate(); } + catch (e) { await showToast({ style: Toast.Style.Failure, title: "Generate failed", message: String(e) }); } + }} + /> + {pwd && ( + { await Clipboard.copy(pwd, { concealed: true }); await showToast({ style: Toast.Style.Success, title: "Copied" }); }} + /> + )} + {pwd && Clipboard.paste(pwd)} />} + + } + > + + update("mode", v as Mode)}> + + + + {settings.mode === "chars" ? ( + <> + update("length", v)} /> + update("symbols", v)} /> + update("onlyNumbers", v)} /> + update("nonconfusables", v)} /> + + ) : ( + <> + update("words", v)} /> + update("separator", v)} /> + update("capitalize", v)} /> + update("includeNumber", v)} /> + + )} + + ); +} diff --git a/extensions/bitwarden/src/lock-vault.tsx b/extensions/bitwarden/src/lock-vault.tsx new file mode 100644 index 00000000..1b8d858e --- /dev/null +++ b/extensions/bitwarden/src/lock-vault.tsx @@ -0,0 +1,14 @@ +import { showToast, Toast } from "@vicinae/api"; +import { RbwCli } from "./api/rbw"; +import { Vault } from "./api/vault"; +import { getPrefs, resolveCliPath } from "./utils/prefs"; +import { clearAllReprompt } from "./api/session-store"; + +export default async function () { + const prefs = getPrefs(); + const cli = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const vault = new Vault(cli); + try { await vault.lock(); } catch { /* best effort */ } + await clearAllReprompt(); + await showToast({ style: Toast.Style.Success, title: "Vault locked" }); +} diff --git a/extensions/bitwarden/src/logout-vault.tsx b/extensions/bitwarden/src/logout-vault.tsx new file mode 100644 index 00000000..f74b1e4a --- /dev/null +++ b/extensions/bitwarden/src/logout-vault.tsx @@ -0,0 +1,22 @@ +import { showToast, Toast, confirmAlert, Alert } from "@vicinae/api"; +import { RbwCli } from "./api/rbw"; +import { Vault } from "./api/vault"; +import { getPrefs, resolveCliPath } from "./utils/prefs"; +import { clearCache, clearAllReprompt } from "./api/session-store"; + +export default async function () { + const ok = await confirmAlert({ + title: "Log out of Bitwarden?", + message: "You will need to re-enter your API keys and master password.", + primaryAction: { title: "Log out", style: Alert.ActionStyle.Destructive }, + }); + if (!ok) return; + + const prefs = getPrefs(); + const cli = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const vault = new Vault(cli); + try { await vault.logout(); } catch { /* best effort */ } + await clearCache(); + await clearAllReprompt(); + await showToast({ style: Toast.Style.Success, title: "Logged out" }); +} diff --git a/extensions/bitwarden/src/search.tsx b/extensions/bitwarden/src/search.tsx new file mode 100644 index 00000000..c96e7ad4 --- /dev/null +++ b/extensions/bitwarden/src/search.tsx @@ -0,0 +1,28 @@ +import { SessionProvider, useSession } from "./context/session-provider"; +import { VaultProvider } from "./context/vault-provider"; +import { ApiKeyLoginForm } from "./components/api-key-login-form"; +import { UnlockForm } from "./components/unlock-form"; +import { ItemList } from "./components/item-list"; +import { Detail, List } from "@vicinae/api"; + +function Inner() { + const { state, invalidateSession } = useSession(); + if (state.kind === "loading") return ; + if (state.kind === "needs-cli") + return ; + if (state.kind === "needs-login") return ; + if (state.kind === "needs-unlock") return ; + return ( + void invalidateSession()}> + + + ); +} + +export default function Search() { + return ( + + + + ); +} diff --git a/extensions/bitwarden/src/sync-vault.tsx b/extensions/bitwarden/src/sync-vault.tsx new file mode 100644 index 00000000..0f739771 --- /dev/null +++ b/extensions/bitwarden/src/sync-vault.tsx @@ -0,0 +1,28 @@ +import { showToast, Toast } from "@vicinae/api"; +import { RbwCli } from "./api/rbw"; +import { Vault } from "./api/vault"; +import { getPrefs, resolveCliPath } from "./utils/prefs"; +import { setLastSync, clearCache } from "./api/session-store"; + +export default async function () { + const prefs = getPrefs(); + const cli = new RbwCli({ cliPath: resolveCliPath(prefs), serverCertsPath: prefs.serverCertsPath || undefined }); + const vault = new Vault(cli); + const status = await vault.status(); + if (!status || status.status !== "unlocked") { + await showToast({ style: Toast.Style.Failure, title: "Not unlocked", message: "Open Search Vault and unlock first." }); + return; + } + const toast = await showToast({ style: Toast.Style.Animated, title: "Syncing vault…" }); + try { + await vault.sync(); + await setLastSync(); + await clearCache(); + toast.style = Toast.Style.Success; + toast.title = "Vault synced"; + } catch (e) { + toast.style = Toast.Style.Failure; + toast.title = "Sync failed"; + toast.message = String(e); + } +} diff --git a/extensions/bitwarden/src/types/bitwarden.ts b/extensions/bitwarden/src/types/bitwarden.ts new file mode 100644 index 00000000..662c5c0f --- /dev/null +++ b/extensions/bitwarden/src/types/bitwarden.ts @@ -0,0 +1,118 @@ +export type ItemType = 1 | 2 | 3 | 4; // 1 login, 2 secure note, 3 card, 4 identity + +export interface Uri { + match: number | null; + uri: string; +} + +export interface Login { + username: string | null; + password: string | null; + totp: string | null; + uris: Uri[] | null; + passwordRevisionDate: string | null; + /** Cache-only marker: true if the original item had a TOTP secret. Set by stripSensitive. */ + hasTotp?: boolean; + /** Cache-only marker: true if the original item had a password. Set by stripSensitive. */ + hasPassword?: boolean; +} + +export interface Card { + cardholderName: string | null; + brand: string | null; + number: string | null; + expMonth: string | null; + expYear: string | null; + code: string | null; +} + +export interface Identity { + title: string | null; + firstName: string | null; + middleName: string | null; + lastName: string | null; + address1: string | null; + address2: string | null; + address3: string | null; + city: string | null; + state: string | null; + postalCode: string | null; + country: string | null; + company: string | null; + email: string | null; + phone: string | null; + ssn: string | null; + username: string | null; + passportNumber: string | null; + licenseNumber: string | null; +} + +export interface SecureNote { + type: 0; +} + +export interface Field { + name: string; + value: string | null; + type: 0 | 1 | 2 | 3; // text, hidden, boolean, linked +} + +export interface Item { + object: "item"; + id: string; + organizationId: string | null; + folderId: string | null; + type: ItemType; + reprompt: 0 | 1; + name: string; + notes: string | null; + favorite: boolean; + fields?: Field[]; + login?: Login; + card?: Card; + identity?: Identity; + secureNote?: SecureNote; + collectionIds: string[] | null; + revisionDate: string; + creationDate: string; + deletedDate: string | null; +} + +export interface Folder { + object: "folder"; + id: string | null; + name: string; +} + +export type SendType = 0 | 1; // 0 text, 1 file + +export interface Send { + object: "send"; + id: string; + accessId: string; + type: SendType; + name: string; + notes: string | null; + key: string; + maxAccessCount: number | null; + accessCount: number; + revisionDate: string; + expirationDate: string | null; + deletionDate: string; + password: string | null; + disabled: boolean; + hideEmail: boolean; + text?: { text: string; hidden: boolean }; + file?: { fileName: string; size: string; sizeName: string }; + accessUrl: string; +} + +export type LockStatus = "unauthenticated" | "locked" | "unlocked"; + +export interface Status { + serverUrl: string | null; + lastSync: string | null; + userEmail: string | null; + userId: string | null; + status: LockStatus; +} diff --git a/extensions/bitwarden/src/types/prefs.ts b/extensions/bitwarden/src/types/prefs.ts new file mode 100644 index 00000000..9ece0efe --- /dev/null +++ b/extensions/bitwarden/src/types/prefs.ts @@ -0,0 +1,12 @@ +export interface Preferences { + clientId: string; + clientSecret: string; + cliPath: string; + serverUrl: string; + serverCertsPath: string; + syncOnLaunch: boolean; + fetchFavicons: boolean; + repromptIgnoreDuration: "0" | "30000" | "60000" | "300000" | "900000" | "never"; + windowActionOnCopy: "close" | "keepOpen"; + shouldCacheVaultItems: boolean; +} diff --git a/extensions/bitwarden/src/utils/cache.ts b/extensions/bitwarden/src/utils/cache.ts new file mode 100644 index 00000000..7346736c --- /dev/null +++ b/extensions/bitwarden/src/utils/cache.ts @@ -0,0 +1,19 @@ +import type { Item } from "../types/bitwarden"; + +export function stripSensitive(items: Item[]): Item[] { + return items.map((i) => { + const out: Item = { ...i, notes: null }; + delete out.fields; + if (out.login) { + const hasTotp = !!out.login.totp; + const hasPassword = !!out.login.password; + out.login = { ...out.login, password: null, totp: null, hasTotp, hasPassword }; + } + if (out.card) delete out.card; + if (out.secureNote) delete out.secureNote; + if (out.identity) { + out.identity = { ...out.identity, ssn: null, passportNumber: null, licenseNumber: null }; + } + return out; + }); +} diff --git a/extensions/bitwarden/src/utils/crypto.ts b/extensions/bitwarden/src/utils/crypto.ts new file mode 100644 index 00000000..679bd5ee --- /dev/null +++ b/extensions/bitwarden/src/utils/crypto.ts @@ -0,0 +1,54 @@ +import { pbkdf2, randomBytes, createCipheriv, createDecipheriv } from "node:crypto"; +import { promisify } from "node:util"; + +const pbkdf2Async = promisify(pbkdf2); + +const ITERATIONS = 600_000; +const KEY_LEN = 32; +const SALT_LEN = 16; +const IV_LEN = 12; +const TAG_LEN = 16; + +export interface EncryptedBlob { + v: 1; + salt: string; // base64 + iv: string; // base64 + ciphertext: string; // base64 + tag: string; // base64 +} + +export async function deriveKey(password: string, salt: Buffer): Promise { + return pbkdf2Async(password, salt, ITERATIONS, KEY_LEN, "sha256"); +} + +export async function encrypt(plaintext: string, password: string): Promise { + const salt = randomBytes(SALT_LEN); + const iv = randomBytes(IV_LEN); + const key = await deriveKey(password, salt); + const cipher = createCipheriv("aes-256-gcm", key, iv); + const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + const tag = cipher.getAuthTag(); + return { + v: 1, + salt: salt.toString("base64"), + iv: iv.toString("base64"), + ciphertext: ct.toString("base64"), + tag: tag.toString("base64"), + }; +} + +export async function decrypt(blob: EncryptedBlob, password: string): Promise { + if (blob.v !== 1) throw new Error("Unsupported blob version"); + const salt = Buffer.from(blob.salt, "base64"); + const iv = Buffer.from(blob.iv, "base64"); + const ct = Buffer.from(blob.ciphertext, "base64"); + const tag = Buffer.from(blob.tag, "base64"); + if (salt.length !== SALT_LEN) throw new Error("Invalid salt"); + if (iv.length !== IV_LEN) throw new Error("Invalid IV"); + if (tag.length !== TAG_LEN) throw new Error("Invalid auth tag"); + const key = await deriveKey(password, salt); + const decipher = createDecipheriv("aes-256-gcm", key, iv); + decipher.setAuthTag(tag); + const pt = Buffer.concat([decipher.update(ct), decipher.final()]); + return pt.toString("utf8"); +} diff --git a/extensions/bitwarden/src/utils/mutex.ts b/extensions/bitwarden/src/utils/mutex.ts new file mode 100644 index 00000000..a35d3fb3 --- /dev/null +++ b/extensions/bitwarden/src/utils/mutex.ts @@ -0,0 +1,9 @@ +export class Mutex { + private chain: Promise = Promise.resolve(); + + run(fn: () => Promise): Promise { + const next = this.chain.then(fn, fn); + this.chain = next.catch(() => undefined); + return next as Promise; + } +} diff --git a/extensions/bitwarden/src/utils/prefs.ts b/extensions/bitwarden/src/utils/prefs.ts new file mode 100644 index 00000000..b41a4e49 --- /dev/null +++ b/extensions/bitwarden/src/utils/prefs.ts @@ -0,0 +1,66 @@ +import { getPreferenceValues } from "@vicinae/api"; +import { mkdirSync, writeFileSync, chmodSync, existsSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; +import type { Preferences } from "../types/prefs"; + +export function getPrefs(): Preferences { + return getPreferenceValues(); +} + +export function resolveCliPath(prefs: Preferences): string { + return prefs.cliPath?.trim() || "rbw"; +} + +export function repromptGraceMs(prefs: Preferences): number | "never" { + if (prefs.repromptIgnoreDuration === "never") return "never"; + const n = Number(prefs.repromptIgnoreDuration); + return Number.isFinite(n) ? n : 0; +} + +const PINENTRY_SHIM = `#!/bin/sh +# pinentry-vicinae — Assuan-protocol shim for rbw. +# Emits a percent-encoded form of $RBW_PINENTRY_VALUE on GETPIN, per Assuan rules. +encode() { + awk -v s="$1" 'BEGIN { + gsub(/%/, "%25", s); + gsub(/\\r/, "%0D", s); + gsub(/\\n/, "%0A", s); + printf "%s", s; + }' +} +printf 'OK Pleased to meet you\\n' +while IFS= read -r line; do + case "$line" in + GETPIN*) printf 'D %s\\n' "$(encode "$RBW_PINENTRY_VALUE")"; printf 'OK\\n' ;; + BYE*) printf 'OK closing connection\\n'; exit 0 ;; + *) printf 'OK\\n' ;; + esac +done +`; + +const EDITOR_SHIM = `#!/bin/sh +# editor-vicinae — $EDITOR replacement for rbw add/edit. +# Writes $RBW_EDITOR_PAYLOAD to argv[1]. No prompts. +printf '%s' "$RBW_EDITOR_PAYLOAD" > "$1" +`; + +function writeShim(name: string, source: string): string { + const dir = join(homedir(), ".cache", "vicinae-bw", "shims"); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const path = join(dir, name); + // Always rewrite to keep the shim in sync with the embedded source + // across extension upgrades; cheap (a few hundred bytes). + writeFileSync(path, source); + chmodSync(path, 0o755); + console.log(`[rbw-ext] shim ${name} -> ${path}`); + return path; +} + +export function resolvePinentryShim(): string { + return writeShim("pinentry-vicinae", PINENTRY_SHIM); +} + +export function resolveEditorShim(): string { + return writeShim("editor-vicinae", EDITOR_SHIM); +} diff --git a/extensions/bitwarden/src/utils/totp.ts b/extensions/bitwarden/src/utils/totp.ts new file mode 100644 index 00000000..a40a542e --- /dev/null +++ b/extensions/bitwarden/src/utils/totp.ts @@ -0,0 +1,6 @@ +export function secondsRemaining(nowMs: number, periodSeconds: number): number { + const periodMs = periodSeconds * 1000; + const elapsed = nowMs % periodMs; + const remainingMs = periodMs - elapsed; + return Math.ceil(remainingMs / 1000); +} diff --git a/extensions/bitwarden/src/utils/window.ts b/extensions/bitwarden/src/utils/window.ts new file mode 100644 index 00000000..b14f1a9e --- /dev/null +++ b/extensions/bitwarden/src/utils/window.ts @@ -0,0 +1,10 @@ +import { closeMainWindow, popToRoot, getPreferenceValues } from "@vicinae/api"; +import type { Preferences } from "../types/prefs"; + +export async function applyWindowAction(): Promise { + const prefs = getPreferenceValues(); + if (prefs.windowActionOnCopy === "close") { + await popToRoot(); + await closeMainWindow(); + } +} diff --git a/extensions/bitwarden/tests/api/errors.test.ts b/extensions/bitwarden/tests/api/errors.test.ts new file mode 100644 index 00000000..d755a487 --- /dev/null +++ b/extensions/bitwarden/tests/api/errors.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { classifyRbwStderr, AuthFailed, Locked, NotLoggedIn, ItemNotFound, CliInvocationError } from "../../src/api/errors"; + +describe("classifyRbwStderr", () => { + it("locks when agent reports locked", () => { + expect(classifyRbwStderr("Error: agent is not running", 1)).toBeInstanceOf(Locked); + expect(classifyRbwStderr("Error: failed to communicate with rbw-agent", 1)).toBeInstanceOf(Locked); + }); + + it("auth-failed on bad password / login error", () => { + expect(classifyRbwStderr("Error: failed to authenticate", 1)).toBeInstanceOf(AuthFailed); + expect(classifyRbwStderr("Error: invalid email or password", 1)).toBeInstanceOf(AuthFailed); + }); + + it("not-logged-in when config has no email", () => { + expect(classifyRbwStderr("Error: account is not configured", 1)).toBeInstanceOf(NotLoggedIn); + expect(classifyRbwStderr("Error: failed to load db: No such file or directory", 1)).toBeInstanceOf(NotLoggedIn); + }); + + it("item-not-found on missing entry", () => { + expect(classifyRbwStderr("Error: failed to find entry: foo", 1)).toBeInstanceOf(ItemNotFound); + }); + + it("falls through to CliInvocationError", () => { + const e = classifyRbwStderr("Error: something else weird", 7); + expect(e).toBeInstanceOf(CliInvocationError); + expect((e as CliInvocationError).exitCode).toBe(7); + }); +}); diff --git a/extensions/bitwarden/tests/api/rbw-adapter.test.ts b/extensions/bitwarden/tests/api/rbw-adapter.test.ts new file mode 100644 index 00000000..17304e28 --- /dev/null +++ b/extensions/bitwarden/tests/api/rbw-adapter.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from "vitest"; +import { adaptListEntry, adaptGetEntry, deriveFolders, type RbwListEntry, type RbwGetEntry } from "../../src/api/rbw-adapter"; + +const sampleList: RbwListEntry = { + id: "abc", name: "Acme", user: "alice", folder: "Work", uris: ["https://acme.example"], type: "Login", +}; + +const sampleGet: RbwGetEntry = { + id: "abc", + name: "Acme", + folder: "Work", + data: { username: "alice", password: "p4ss", totp: "JBSW", uris: ["https://acme.example"] }, + fields: [{ name: "tag", value: "login", type: "text" }], + notes: "n", + history: [], +}; + +describe("adaptListEntry", () => { + it("maps a Login entry to a sparse Item", () => { + const item = adaptListEntry(sampleList); + expect(item.id).toBe("abc"); + expect(item.type).toBe(1); + expect(item.folderId).toBe("Work"); + expect(item.login?.username).toBe("alice"); + expect(item.login?.password).toBeNull(); + expect(item.login?.uris?.[0]).toEqual({ uri: "https://acme.example", match: null }); + }); + + it("translates rbw type strings", () => { + expect(adaptListEntry({ ...sampleList, type: "SecureNote" }).type).toBe(2); + expect(adaptListEntry({ ...sampleList, type: "Card" }).type).toBe(3); + expect(adaptListEntry({ ...sampleList, type: "Identity" }).type).toBe(4); + }); + + it("emits null folderId for unfoldered entries", () => { + expect(adaptListEntry({ ...sampleList, folder: null }).folderId).toBeNull(); + }); +}); + +describe("adaptGetEntry", () => { + it("maps a full Login entry with password and totp", () => { + const item = adaptGetEntry(sampleGet); + expect(item.login?.password).toBe("p4ss"); + expect(item.login?.totp).toBe("JBSW"); + expect(item.notes).toBe("n"); + expect(item.fields?.[0]).toEqual({ name: "tag", value: "login", type: 0 }); + }); + + it("survives missing data fields", () => { + const empty: RbwGetEntry = { id: "x", name: "x", folder: null, data: { username: null, password: null, totp: null, uris: [] }, fields: [], notes: null, history: [] }; + const item = adaptGetEntry(empty); + expect(item.login?.username).toBeNull(); + expect(item.login?.password).toBeNull(); + expect(item.fields).toEqual([]); + }); +}); + +describe("deriveFolders", () => { + it("emits distinct non-null folders sorted by name", () => { + const folders = deriveFolders([ + { ...sampleList, folder: "Work" }, + { ...sampleList, folder: null }, + { ...sampleList, folder: "Personal" }, + { ...sampleList, folder: "Work" }, + ]); + expect(folders.map((f) => f.name)).toEqual(["Personal", "Work"]); + expect(folders[0]).toEqual({ object: "folder", id: "Personal", name: "Personal" }); + }); +}); diff --git a/extensions/bitwarden/tests/api/rbw.test.ts b/extensions/bitwarden/tests/api/rbw.test.ts new file mode 100644 index 00000000..a75bfe22 --- /dev/null +++ b/extensions/bitwarden/tests/api/rbw.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from "vitest"; +import { RbwCli } from "../../src/api/rbw"; +import { mkdtempSync, writeFileSync, chmodSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +// Build a tiny shell script that echoes its argv and selected env to stdout, then exits. +function fakeRbw(): string { + const dir = mkdtempSync(join(tmpdir(), "fake-rbw-")); + const path = join(dir, "rbw"); + writeFileSync(path, `#!/bin/sh +echo "ARGS:$*" +echo "SSL:$SSL_CERT_FILE" +echo "PIN:$RBW_PINENTRY_VALUE" +exit 0 +`); + chmodSync(path, 0o755); + return path; +} + +describe("RbwCli", () => { + it("invokes rbw with args and returns stdout", async () => { + const cli = new RbwCli({ cliPath: fakeRbw() }); + const out = await cli.text(["list", "--raw"]); + expect(out).toContain("ARGS:list --raw"); + }); + + it("injects SSL_CERT_FILE when serverCertsPath set", async () => { + const cli = new RbwCli({ cliPath: fakeRbw(), serverCertsPath: "/etc/foo.pem" }); + const out = await cli.text(["status"]); + expect(out).toContain("SSL:/etc/foo.pem"); + }); + + it("withEnv merges env on the new instance only", async () => { + const base = new RbwCli({ cliPath: fakeRbw() }); + const env = base.withEnv({ RBW_PINENTRY_VALUE: "topsecret" }); + expect((await env.text(["unlock"]))).toContain("PIN:topsecret"); + expect((await base.text(["unlock"]))).toContain("PIN:\n"); + }); + + it("readJson parses raw output", async () => { + const dir = mkdtempSync(join(tmpdir(), "fake-rbw-")); + const path = join(dir, "rbw"); + writeFileSync(path, `#!/bin/sh\necho '{"hello":"world"}'\n`); + chmodSync(path, 0o755); + const cli = new RbwCli({ cliPath: path }); + expect(await cli.readJson(["list", "--raw"])).toEqual({ hello: "world" }); + }); + + it("rejects with classified error on non-zero exit", async () => { + const dir = mkdtempSync(join(tmpdir(), "fake-rbw-")); + const path = join(dir, "rbw"); + writeFileSync(path, `#!/bin/sh\necho "Error: failed to find entry: x" >&2\nexit 1\n`); + chmodSync(path, 0o755); + const cli = new RbwCli({ cliPath: path }); + const { ItemNotFound } = await import("../../src/api/errors"); + await expect(cli.text(["get", "x"])).rejects.toBeInstanceOf(ItemNotFound); + }); + + it("throws BwNotFound on ENOENT", async () => { + const cli = new RbwCli({ cliPath: "/nonexistent/rbw-binary-xyz" }); + const { BwNotFound } = await import("../../src/api/errors"); + await expect(cli.text(["status"])).rejects.toBeInstanceOf(BwNotFound); + }); +}); diff --git a/extensions/bitwarden/tests/api/scripts.test.ts b/extensions/bitwarden/tests/api/scripts.test.ts new file mode 100644 index 00000000..edf3ca4b --- /dev/null +++ b/extensions/bitwarden/tests/api/scripts.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect, vi } from "vitest"; +import { spawn } from "node:child_process"; +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +vi.mock("@vicinae/api", () => ({ getPreferenceValues: vi.fn(() => ({})) })); +import { resolvePinentryShim, resolveEditorShim } from "../../src/utils/prefs"; + +function runShim(bin: string, env: Record, stdin: string, args: string[] = []): Promise<{stdout: string; code: number | null}> { + return new Promise((res) => { + const p = spawn(bin, args, { env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"] }); + let out = ""; + p.stdout.on("data", (d) => (out += d.toString())); + p.stdin.on("error", () => { /* shim may close stdin before we write (editor shim exits immediately) */ }); + p.on("close", (code) => res({ stdout: out, code })); + if (stdin.length > 0) p.stdin.write(stdin); + p.stdin.end(); + }); +} + +describe("pinentry-vicinae shim (materialized)", () => { + it("emits master password on GETPIN", async () => { + const path = resolvePinentryShim(); + const { stdout, code } = await runShim(path, { RBW_PINENTRY_VALUE: "hunter2" }, "GETPIN\nBYE\n"); + expect(stdout).toMatch(/^OK Pleased to meet you\n/); + expect(stdout).toContain("D hunter2\n"); + expect(stdout).toContain("OK\n"); + expect(code).toBe(0); + }); + + it("acknowledges unknown commands with OK", async () => { + const path = resolvePinentryShim(); + const { stdout } = await runShim(path, { RBW_PINENTRY_VALUE: "x" }, "SETDESC something\nBYE\n"); + expect(stdout.match(/(?:^|\n)OK\b/g)?.length).toBeGreaterThanOrEqual(2); + }); + + it("percent-encodes %, CR, and LF in the master password", async () => { + const path = resolvePinentryShim(); + const { stdout } = await runShim(path, { RBW_PINENTRY_VALUE: "p%a\nb\rc" }, "GETPIN\nBYE\n"); + expect(stdout).toContain("D p%25a%0Ab%0Dc\n"); + }); +}); + +describe("editor-vicinae shim (materialized)", () => { + it("writes RBW_EDITOR_PAYLOAD to argv[1]", async () => { + const path = resolveEditorShim(); + const dir = mkdtempSync(join(tmpdir(), "editor-shim-")); + const file = join(dir, "rbw-edit.txt"); + writeFileSync(file, ""); + const { code } = await runShim(path, { RBW_EDITOR_PAYLOAD: "secretpass\n\nthe note body" }, "", [file]); + expect(code).toBe(0); + expect(readFileSync(file, "utf8")).toBe("secretpass\n\nthe note body"); + }); + + it("writes empty payload when env unset", async () => { + const path = resolveEditorShim(); + const dir = mkdtempSync(join(tmpdir(), "editor-shim-")); + const file = join(dir, "rbw-edit.txt"); + writeFileSync(file, "stale"); + const { code } = await runShim(path, {}, "", [file]); + expect(code).toBe(0); + expect(readFileSync(file, "utf8")).toBe(""); + }); +}); diff --git a/extensions/bitwarden/tests/api/session-store.test.ts b/extensions/bitwarden/tests/api/session-store.test.ts new file mode 100644 index 00000000..f2837ad5 --- /dev/null +++ b/extensions/bitwarden/tests/api/session-store.test.ts @@ -0,0 +1,135 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { Item, Folder } from "../../src/types/bitwarden"; + +const storage = new Map(); +let savedTokens: { accessToken: string } | null = null; + +vi.mock("@vicinae/api", () => ({ + LocalStorage: { + getItem: vi.fn(async (key: string) => storage.get(key)), + setItem: vi.fn(async (key: string, value: string | number | boolean) => { + storage.set(key, value); + }), + removeItem: vi.fn(async (key: string) => { + storage.delete(key); + }), + allItems: vi.fn(async () => Object.fromEntries(storage)), + }, + OAuth: { + RedirectMethod: { Web: "web" }, + PKCEClient: class { + setTokens(opts: { accessToken: string }) { savedTokens = { accessToken: opts.accessToken }; return Promise.resolve(); } + getTokens() { + return Promise.resolve(savedTokens ? { ...savedTokens, updatedAt: new Date(), isExpired: () => false } : undefined); + } + removeTokens() { savedTokens = null; return Promise.resolve(); } + }, + }, +})); + +import { + saveSession, loadSession, clearSession, + saveCache, loadCache, clearCache, + getLastAppliedServerUrl, setLastAppliedServerUrl, + setRepromptTimestamp, getRepromptTimestamp, clearAllReprompt, +} from "../../src/api/session-store"; + +const sampleItem: Item = { + object: "item", id: "i1", organizationId: null, folderId: "f1", type: 1, reprompt: 0, + name: "GitHub", notes: "secret note", favorite: false, + fields: [{ name: "pin", value: "1234", type: 1 }], + login: { username: "u", password: "p", totp: "T", uris: [{ match: null, uri: "https://gh" }], passwordRevisionDate: null }, + collectionIds: null, revisionDate: "", creationDate: "", deletedDate: null, +}; +const sampleFolder: Folder = { object: "folder", id: "f1", name: "Personal" }; + +beforeEach(() => { + storage.clear(); + savedTokens = null; +}); + +describe("session token persistence (rbw: agent-owned)", () => { + it("loadSession returns null — rbw-agent owns unlock state", async () => { + await saveSession("SESSION-XYZ"); + expect(await loadSession()).toBeNull(); + }); + + it("clearSession is a no-op that succeeds", async () => { + await saveSession("SESSION-XYZ"); + await clearSession(); + expect(await loadSession()).toBeNull(); + }); +}); + +describe("vault cache", () => { + it("saveCache + loadCache round-trips items and folders", async () => { + await saveCache([sampleItem], [sampleFolder]); + const out = await loadCache(); + expect(out).not.toBeNull(); + expect(out!.items[0]!.id).toBe("i1"); + expect(out!.folders[0]!.id).toBe("f1"); + }); + + it("saveCache strips sensitive fields before persisting", async () => { + await saveCache([sampleItem], [sampleFolder]); + const out = await loadCache(); + expect(out!.items[0]!.login?.password).toBeNull(); + expect(out!.items[0]!.login?.totp).toBeNull(); + expect(out!.items[0]!.login?.username).toBe("u"); + expect(out!.items[0]!.notes).toBeNull(); + expect(out!.items[0]!.fields).toBeUndefined(); + }); + + it("loadCache returns null when items are missing", async () => { + expect(await loadCache()).toBeNull(); + }); + + it("saveCache stamps a recent mtime", async () => { + const before = Date.now(); + await saveCache([sampleItem], [sampleFolder]); + const out = await loadCache(); + expect(out).not.toBeNull(); + expect(out!.mtime).toBeGreaterThanOrEqual(before); + expect(out!.mtime).toBeLessThanOrEqual(Date.now()); + }); + + it("clearCache removes both items and folders", async () => { + await saveCache([sampleItem], [sampleFolder]); + await clearCache(); + expect(await loadCache()).toBeNull(); + }); +}); + +describe("last-applied server URL", () => { + it("returns null when never set", async () => { + expect(await getLastAppliedServerUrl()).toBeNull(); + }); + + it("round-trips a URL", async () => { + await setLastAppliedServerUrl("https://vault.example.com"); + expect(await getLastAppliedServerUrl()).toBe("https://vault.example.com"); + }); +}); + +describe("reprompt timestamps", () => { + it("setRepromptTimestamp + getRepromptTimestamp round-trip", async () => { + await setRepromptTimestamp("item-1"); + const ts = await getRepromptTimestamp("item-1"); + expect(typeof ts).toBe("number"); + expect(Date.now() - ts!).toBeLessThan(5000); + }); + + it("getRepromptTimestamp returns null when never set", async () => { + expect(await getRepromptTimestamp("never-set")).toBeNull(); + }); + + it("clearAllReprompt removes only reprompt keys", async () => { + await setRepromptTimestamp("a"); + await setRepromptTimestamp("b"); + await setLastAppliedServerUrl("https://x"); + await clearAllReprompt(); + expect(await getRepromptTimestamp("a")).toBeNull(); + expect(await getRepromptTimestamp("b")).toBeNull(); + expect(await getLastAppliedServerUrl()).toBe("https://x"); + }); +}); diff --git a/extensions/bitwarden/tests/api/vault.test.ts b/extensions/bitwarden/tests/api/vault.test.ts new file mode 100644 index 00000000..275c3685 --- /dev/null +++ b/extensions/bitwarden/tests/api/vault.test.ts @@ -0,0 +1,208 @@ +import { describe, it, expect, vi } from "vitest"; + +vi.mock("@vicinae/api", () => ({ getPreferenceValues: vi.fn(() => ({})) })); +import { Vault } from "../../src/api/vault"; + +interface Recorded { args: string[]; runOpts?: unknown; envBefore?: NodeJS.ProcessEnv } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyFn = (...args: any[]) => any; +interface FakeCli { + text: AnyFn; + readText: AnyFn; + json: AnyFn; + readJson: AnyFn; + tryReadText: AnyFn; + withEnv: AnyFn; +} +function fakeRbw(replies: Record): { cli: FakeCli; calls: Recorded[] } { + const calls: Recorded[] = []; + const cli: FakeCli = { + text: vi.fn(async (args: string[], runOpts?: unknown) => { + calls.push({ args, runOpts }); + return replies[args.join(" ")] ?? ""; + }), + readText: vi.fn(async (args: string[]) => { + calls.push({ args }); + return replies[args.join(" ")] ?? ""; + }), + json: vi.fn(async (args: string[]) => { + calls.push({ args }); + const v = replies[args.join(" ")]; + return v ? JSON.parse(v) : undefined; + }), + readJson: vi.fn(async (args: string[]) => { + calls.push({ args }); + const v = replies[args.join(" ")]; + return v ? JSON.parse(v) : undefined; + }), + tryReadText: vi.fn(async (args: string[]) => { + calls.push({ args }); + const v = replies[args.join(" ")]; + return v !== undefined ? { stdout: v, exitCode: 0 } : { stdout: "", exitCode: 1 }; + }), + withEnv: vi.fn(() => cli), + }; + return { cli, calls }; +} + +describe("Vault.status", () => { + it("reports unlocked when rbw unlocked exits 0", async () => { + const { cli } = fakeRbw({ + "config show": '{"email":"a@b.c","base_url":"https://srv/"}', + "unlocked": "", + }); + const v = new Vault(cli as unknown as never); + const s = await v.status(); + expect(s?.status).toBe("unlocked"); + expect(s?.userEmail).toBe("a@b.c"); + expect(s?.serverUrl).toBe("https://srv/"); + }); + + it("reports locked when rbw unlocked exits non-zero but config has email", async () => { + const { cli } = fakeRbw({ "config show": '{"email":"a@b.c","base_url":null}' }); + cli.tryReadText = vi.fn(async (args: string[]) => + args.join(" ") === "unlocked" ? { stdout: "", exitCode: 1 } : { stdout: "", exitCode: 0 } + ); + const v = new Vault(cli as unknown as never); + expect((await v.status())?.status).toBe("locked"); + }); + + it("reports unauthenticated when config has no email", async () => { + const { cli } = fakeRbw({ "config show": '{"email":null,"base_url":null}' }); + cli.tryReadText = vi.fn(async () => ({ stdout: "", exitCode: 1 })); + const v = new Vault(cli as unknown as never); + expect((await v.status())?.status).toBe("unauthenticated"); + }); +}); + +describe("Vault.listItems / listFolders / getItem / getTotp", () => { + const sampleList = '[{"id":"a","name":"x","user":"u","folder":"F","uris":["https://x"],"type":"Login"}]'; + it("listItems calls `rbw list --raw` and adapts", async () => { + const { cli, calls } = fakeRbw({ "list --raw": sampleList }); + const v = new Vault(cli as unknown as never); + const items = await v.listItems(); + expect(calls[0]!.args).toEqual(["list", "--raw"]); + expect(items?.[0]!.name).toBe("x"); + expect(items?.[0]!.login?.username).toBe("u"); + }); + + it("listFolders derives from list", async () => { + const { cli } = fakeRbw({ "list --raw": sampleList }); + const v = new Vault(cli as unknown as never); + expect((await v.listFolders())?.[0]!.name).toBe("F"); + }); + + it("getItem calls `rbw get --raw ` and adapts", async () => { + const { cli, calls } = fakeRbw({ + "get --raw a": '{"id":"a","name":"x","folder":null,"data":{"username":"u","password":"p","totp":null,"uris":[]},"fields":[],"notes":null,"history":[]}', + }); + const v = new Vault(cli as unknown as never); + const item = await v.getItem("a"); + expect(calls[0]!.args).toEqual(["get", "--raw", "a"]); + expect(item?.login?.password).toBe("p"); + }); + + it("getTotp calls `rbw code `", async () => { + const { cli, calls } = fakeRbw({ "code a": "123456\n" }); + const v = new Vault(cli as unknown as never); + expect(await v.getTotp("a")).toBe("123456"); + expect(calls[0]!.args).toEqual(["code", "a"]); + }); +}); + +describe("Vault.generatePassword", () => { + it("chars mode without symbols", async () => { + const { cli, calls } = fakeRbw({ "generate 20 --no-symbols": "abcDEF\n" }); + const v = new Vault(cli as unknown as never); + expect(await v.generatePassword({ mode: "chars", length: 20, symbols: false })).toBe("abcDEF"); + expect(calls[0]!.args).toEqual(["generate", "20", "--no-symbols"]); + }); + + it("chars mode only-numbers + nonconfusables", async () => { + const { cli, calls } = fakeRbw({ "generate 16 --only-numbers --nonconfusables": "1234\n" }); + const v = new Vault(cli as unknown as never); + expect(await v.generatePassword({ mode: "chars", length: 16, symbols: true, onlyNumbers: true, nonconfusables: true })).toBe("1234"); + expect(calls[0]!.args).toEqual(["generate", "16", "--only-numbers", "--nonconfusables"]); + }); + + it("diceware mode", async () => { + const { cli, calls } = fakeRbw({ "generate 5 --diceware": "horse-battery-staple-correct-foo\n" }); + const v = new Vault(cli as unknown as never); + expect(await v.generatePassword({ mode: "diceware", words: 5 })).toBe("horse-battery-staple-correct-foo"); + expect(calls[0]!.args).toEqual(["generate", "5", "--diceware"]); + }); +}); + +describe("Vault.unlock", () => { + it("calls `rbw unlock` and returns the sentinel session", async () => { + const { cli, calls } = fakeRbw({ "unlock": "" }); + const v = new Vault(cli as unknown as never); + expect(await v.unlock("hunter2")).toBe("rbw-agent"); + expect(calls[0]!.args).toEqual(["unlock"]); + }); +}); + +describe("Vault.register", () => { + it("pipes client id and secret to rbw register stdin", async () => { + const { cli, calls } = fakeRbw({ "register": "" }); + const v = new Vault(cli as unknown as never); + await v.register("CID", "CSEC"); + expect(calls[0]!.args).toEqual(["register"]); + expect((calls[0]!.runOpts as { stdin: string }).stdin).toBe("CID\nCSEC\n"); + }); +}); + +describe("Vault.config*", () => { + it("configEmail uses `config set email`", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.configEmail("a@b.c"); + expect(calls[0]!.args).toEqual(["config", "set", "email", "a@b.c"]); + }); + + it("configServer uses `config set base_url`", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.configServer("https://srv"); + expect(calls[0]!.args).toEqual(["config", "set", "base_url", "https://srv"]); + }); +}); + +describe("Vault.lock / logout", () => { + it("lock calls rbw lock", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.lock(); + expect(calls[0]!.args).toEqual(["lock"]); + }); + + it("logout calls stop-agent and purge", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.logout(); + expect(calls.map((c) => c.args)).toEqual([["stop-agent"], ["purge"]]); + }); +}); + +describe("Vault.createItem / deleteItem", () => { + it("createItem builds rbw add args from option fields", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.createItem({ name: "Acme", username: "alice", uri: "https://acme", folderId: "Work" }); + expect(calls[0]!.args).toEqual(["add", "--uri", "https://acme", "--folder", "Work", "Acme", "alice"]); + }); + + it("createItem omits flags when fields absent", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.createItem({ name: "Plain" }); + expect(calls[0]!.args).toEqual(["add", "Plain"]); + }); + + it("deleteItem calls rbw remove", async () => { + const { cli, calls } = fakeRbw({}); + const v = new Vault(cli as unknown as never); + await v.deleteItem("xyz"); + expect(calls[0]!.args).toEqual(["remove", "xyz"]); + }); +}); diff --git a/extensions/bitwarden/tests/utils/cache.test.ts b/extensions/bitwarden/tests/utils/cache.test.ts new file mode 100644 index 00000000..a4082be5 --- /dev/null +++ b/extensions/bitwarden/tests/utils/cache.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from "vitest"; +import { stripSensitive } from "../../src/utils/cache"; +import type { Item } from "../../src/types/bitwarden"; + +const sample: Item = { + object: "item", id: "1", organizationId: null, folderId: "f1", type: 1, reprompt: 0, + name: "GitHub", notes: "secret note", favorite: false, + fields: [{ name: "pin", value: "1234", type: 1 }], + login: { username: "u", password: "p", totp: "JBSWY...", uris: [{ match: null, uri: "https://github.com" }], passwordRevisionDate: null }, + collectionIds: null, revisionDate: "", creationDate: "", deletedDate: null, +}; + +describe("cache.stripSensitive", () => { + it("removes password, totp, notes, custom fields, card, identity", () => { + const stripped = stripSensitive([sample])[0]!; + expect(stripped.notes).toBeNull(); + expect(stripped.fields).toBeUndefined(); + expect(stripped.login?.password).toBeNull(); + expect(stripped.login?.totp).toBeNull(); + expect(stripped.login?.username).toBe("u"); // username kept + expect(stripped.login?.uris).toEqual(sample.login?.uris); + }); + + it("strips card.number/code and identity SSN/passport/license", () => { + const card: Item = { + ...sample, id: "2", type: 3, login: undefined, + card: { cardholderName: "A", brand: "visa", number: "4111", expMonth: "01", expYear: "30", code: "123" }, + }; + const ident: Item = { + ...sample, id: "3", type: 4, login: undefined, + identity: { title: "Mr", firstName: "F", middleName: null, lastName: "L", address1: null, address2: null, address3: null, + city: null, state: null, postalCode: null, country: null, company: null, email: null, phone: null, + ssn: "123-45-6789", username: null, passportNumber: "P1", licenseNumber: "L1" }, + }; + const out = stripSensitive([card, ident]); + expect(out[0]!.card).toBeUndefined(); + expect(out[1]!.identity?.ssn).toBeNull(); + expect(out[1]!.identity?.passportNumber).toBeNull(); + expect(out[1]!.identity?.licenseNumber).toBeNull(); + expect(out[1]!.identity?.firstName).toBe("F"); + }); +}); + diff --git a/extensions/bitwarden/tests/utils/crypto.test.ts b/extensions/bitwarden/tests/utils/crypto.test.ts new file mode 100644 index 00000000..b7697e34 --- /dev/null +++ b/extensions/bitwarden/tests/utils/crypto.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect } from "vitest"; +import { deriveKey, encrypt, decrypt, type EncryptedBlob } from "../../src/utils/crypto"; + +describe("crypto", () => { + it("deriveKey returns 32 bytes for the same password+salt deterministically", async () => { + const salt = Buffer.alloc(16, 1); + const k1 = await deriveKey("hunter2", salt); + const k2 = await deriveKey("hunter2", salt); + expect(k1.length).toBe(32); + expect(k1.equals(k2)).toBe(true); + }); + + it("deriveKey produces different keys for different passwords", async () => { + const salt = Buffer.alloc(16, 2); + const k1 = await deriveKey("a", salt); + const k2 = await deriveKey("b", salt); + expect(k1.equals(k2)).toBe(false); + }); + + it("encrypt + decrypt round-trips", async () => { + const blob = await encrypt("hello world", "pwd"); + const out = await decrypt(blob, "pwd"); + expect(out).toBe("hello world"); + }); + + it("decrypt with wrong password throws", async () => { + const blob = await encrypt("secret", "right"); + await expect(decrypt(blob, "wrong")).rejects.toThrow(); + }); + + it("decrypt rejects tampered ciphertext", async () => { + const blob = await encrypt("secret", "pwd"); + const tampered: EncryptedBlob = { + ...blob, + ciphertext: blob.ciphertext.replace(/.$/, (c) => (c === "a" ? "b" : "a")), + }; + await expect(decrypt(tampered, "pwd")).rejects.toThrow(); + }); + + it("decrypt rejects malformed salt length", async () => { + const blob = await encrypt("secret", "pwd"); + const bad: EncryptedBlob = { ...blob, salt: Buffer.alloc(8).toString("base64") }; + await expect(decrypt(bad, "pwd")).rejects.toThrow("Invalid salt"); + }); + + it("decrypt rejects malformed IV length", async () => { + const blob = await encrypt("secret", "pwd"); + const bad: EncryptedBlob = { ...blob, iv: Buffer.alloc(8).toString("base64") }; + await expect(decrypt(bad, "pwd")).rejects.toThrow("Invalid IV"); + }); + + it("decrypt rejects tampered IV", async () => { + const blob = await encrypt("secret", "pwd"); + const iv = Buffer.from(blob.iv, "base64"); + iv[0] = (iv[0] ?? 0) ^ 0xff; + await expect(decrypt({ ...blob, iv: iv.toString("base64") }, "pwd")).rejects.toThrow(); + }); +}); diff --git a/extensions/bitwarden/tests/utils/mutex.test.ts b/extensions/bitwarden/tests/utils/mutex.test.ts new file mode 100644 index 00000000..ccdcf8d7 --- /dev/null +++ b/extensions/bitwarden/tests/utils/mutex.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { Mutex } from "../../src/utils/mutex"; + +describe("Mutex", () => { + it("serializes async work in submission order", async () => { + const m = new Mutex(); + const out: number[] = []; + const tasks = [10, 5, 1].map((delay, i) => + m.run(async () => { + await new Promise((r) => setTimeout(r, delay)); + out.push(i); + }), + ); + await Promise.all(tasks); + expect(out).toEqual([0, 1, 2]); + }); + + it("propagates errors without breaking the queue", async () => { + const m = new Mutex(); + await expect(m.run(async () => { throw new Error("boom"); })).rejects.toThrow("boom"); + const v = await m.run(async () => 42); + expect(v).toBe(42); + }); +}); diff --git a/extensions/bitwarden/tests/utils/prefs.test.ts b/extensions/bitwarden/tests/utils/prefs.test.ts new file mode 100644 index 00000000..328c3939 --- /dev/null +++ b/extensions/bitwarden/tests/utils/prefs.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect, vi } from "vitest"; +import { statSync, readFileSync } from "node:fs"; + +vi.mock("@vicinae/api", () => ({ getPreferenceValues: vi.fn(() => ({})) })); +import { resolveCliPath, resolvePinentryShim, resolveEditorShim } from "../../src/utils/prefs"; + +describe("resolveCliPath", () => { + it("defaults to rbw when pref is empty", () => { + expect(resolveCliPath({ cliPath: "" } as never)).toBe("rbw"); + }); + it("honors a custom path", () => { + expect(resolveCliPath({ cliPath: "/opt/rbw" } as never)).toBe("/opt/rbw"); + }); +}); + +describe("resolveShim", () => { + it("materializes pinentry shim under ~/.cache/vicinae-bw as executable", () => { + const p = resolvePinentryShim(); + expect(p.endsWith("/vicinae-bw/shims/pinentry-vicinae")).toBe(true); + expect((statSync(p).mode & 0o111) !== 0).toBe(true); + expect(readFileSync(p, "utf8")).toContain("Assuan-protocol shim"); + }); + it("materializes editor shim under ~/.cache/vicinae-bw as executable", () => { + const p = resolveEditorShim(); + expect(p.endsWith("/vicinae-bw/shims/editor-vicinae")).toBe(true); + expect((statSync(p).mode & 0o111) !== 0).toBe(true); + expect(readFileSync(p, "utf8")).toContain("EDITOR replacement"); + }); +}); diff --git a/extensions/bitwarden/tests/utils/totp.test.ts b/extensions/bitwarden/tests/utils/totp.test.ts new file mode 100644 index 00000000..d772212f --- /dev/null +++ b/extensions/bitwarden/tests/utils/totp.test.ts @@ -0,0 +1,17 @@ +import { describe, it, expect } from "vitest"; +import { secondsRemaining } from "../../src/utils/totp"; + +describe("totp.secondsRemaining", () => { + it("returns 30 at the start of a 30s window", () => { + expect(secondsRemaining(0, 30)).toBe(30); + expect(secondsRemaining(30_000, 30)).toBe(30); + }); + + it("returns 1 just before a window ends", () => { + expect(secondsRemaining(29_000, 30)).toBe(1); + }); + + it("respects custom periods", () => { + expect(secondsRemaining(45_000, 60)).toBe(15); + }); +}); diff --git a/extensions/bitwarden/tsconfig.json b/extensions/bitwarden/tsconfig.json new file mode 100644 index 00000000..d4303052 --- /dev/null +++ b/extensions/bitwarden/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "lib": ["ES2022", "DOM"], + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*", "tests/**/*"] +} diff --git a/extensions/bitwarden/vitest.config.ts b/extensions/bitwarden/vitest.config.ts new file mode 100644 index 00000000..4b1f9af9 --- /dev/null +++ b/extensions/bitwarden/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["tests/**/*.test.ts"], + }, +});