From 59f2264a2e17f4b70b2c19da38fabf3719d17c82 Mon Sep 17 00:00:00 2001 From: Dokril Date: Tue, 7 Jul 2026 21:19:41 +0300 Subject: [PATCH] Clarify active Windows client architecture --- README.md | 54 + apps/windows-client/.gitignore | 4 + apps/windows-client/README.md | 116 + apps/windows-client/index.html | 12 + apps/windows-client/package-lock.json | 2106 ++++++++ apps/windows-client/package.json | 27 + .../scripts/install-control-app.ps1 | 79 + .../scripts/install-proxyfier.ps1 | 96 + .../scripts/install-singbox.ps1 | 96 + apps/windows-client/src-tauri/Cargo.lock | 4390 +++++++++++++++++ apps/windows-client/src-tauri/Cargo.toml | 19 + apps/windows-client/src-tauri/build.rs | 4 + .../src-tauri/capabilities/default.json | 7 + .../src-tauri/gen/schemas/acl-manifests.json | 1 + .../src-tauri/gen/schemas/capabilities.json | 1 + .../src-tauri/gen/schemas/desktop-schema.json | 2292 +++++++++ .../src-tauri/gen/schemas/windows-schema.json | 2292 +++++++++ .../src-tauri/icons/128x128.png | Bin 0 -> 1726 bytes .../src-tauri/icons/128x128@2x.png | Bin 0 -> 3213 bytes apps/windows-client/src-tauri/icons/32x32.png | Bin 0 -> 940 bytes apps/windows-client/src-tauri/icons/icon.ico | Bin 0 -> 16958 bytes apps/windows-client/src-tauri/src/activity.rs | 23 + .../src-tauri/src/adapters/proxifyre.rs | 242 + .../src-tauri/src/adapters/proxy_router.rs | 67 + .../src-tauri/src/adapters/singbox.rs | 358 ++ apps/windows-client/src-tauri/src/commands.rs | 971 ++++ .../src-tauri/src/component_detection.rs | 414 ++ apps/windows-client/src-tauri/src/helper.rs | 184 + apps/windows-client/src-tauri/src/lib.rs | 5 + apps/windows-client/src-tauri/src/main.rs | 42 + apps/windows-client/src-tauri/src/models.rs | 167 + apps/windows-client/src-tauri/src/storage.rs | 187 + .../src-tauri/src/validation.rs | 222 + apps/windows-client/src-tauri/tauri.conf.json | 37 + .../src-tauri/tests/command_tests.rs | 442 ++ .../tests/component_detection_tests.rs | 141 + .../src-tauri/tests/domain_tests.rs | 130 + .../src-tauri/tests/helper_tests.rs | 139 + .../tests/proxifyre_adapter_tests.rs | 178 + .../src-tauri/tests/singbox_adapter_tests.rs | 282 ++ .../src-tauri/tests/storage_tests.rs | 195 + apps/windows-client/src/api/tauriCommands.ts | 79 + apps/windows-client/src/app/App.tsx | 462 ++ apps/windows-client/src/domain/types.ts | 77 + apps/windows-client/src/main.tsx | 11 + apps/windows-client/src/styles/app.css | 325 ++ apps/windows-client/tsconfig.json | 22 + apps/windows-client/vite.config.ts | 18 + docs/goals/windows-modular-client/EVIDENCE.md | 1342 +++++ docs/goals/windows-modular-client/GOAL.md | 16 + docs/goals/windows-modular-client/PLAN.md | 506 ++ docs/roadmap.md | 33 +- .../plans/2026-05-21-windows-client.md | 7 + .../specs/2026-05-21-windows-client-design.md | 7 + docs/windows-client-product-tech-brief.md | 635 +++ 55 files changed, 19554 insertions(+), 8 deletions(-) create mode 100644 apps/windows-client/.gitignore create mode 100644 apps/windows-client/README.md create mode 100644 apps/windows-client/index.html create mode 100644 apps/windows-client/package-lock.json create mode 100644 apps/windows-client/package.json create mode 100644 apps/windows-client/scripts/install-control-app.ps1 create mode 100644 apps/windows-client/scripts/install-proxyfier.ps1 create mode 100644 apps/windows-client/scripts/install-singbox.ps1 create mode 100644 apps/windows-client/src-tauri/Cargo.lock create mode 100644 apps/windows-client/src-tauri/Cargo.toml create mode 100644 apps/windows-client/src-tauri/build.rs create mode 100644 apps/windows-client/src-tauri/capabilities/default.json create mode 100644 apps/windows-client/src-tauri/gen/schemas/acl-manifests.json create mode 100644 apps/windows-client/src-tauri/gen/schemas/capabilities.json create mode 100644 apps/windows-client/src-tauri/gen/schemas/desktop-schema.json create mode 100644 apps/windows-client/src-tauri/gen/schemas/windows-schema.json create mode 100644 apps/windows-client/src-tauri/icons/128x128.png create mode 100644 apps/windows-client/src-tauri/icons/128x128@2x.png create mode 100644 apps/windows-client/src-tauri/icons/32x32.png create mode 100644 apps/windows-client/src-tauri/icons/icon.ico create mode 100644 apps/windows-client/src-tauri/src/activity.rs create mode 100644 apps/windows-client/src-tauri/src/adapters/proxifyre.rs create mode 100644 apps/windows-client/src-tauri/src/adapters/proxy_router.rs create mode 100644 apps/windows-client/src-tauri/src/adapters/singbox.rs create mode 100644 apps/windows-client/src-tauri/src/commands.rs create mode 100644 apps/windows-client/src-tauri/src/component_detection.rs create mode 100644 apps/windows-client/src-tauri/src/helper.rs create mode 100644 apps/windows-client/src-tauri/src/lib.rs create mode 100644 apps/windows-client/src-tauri/src/main.rs create mode 100644 apps/windows-client/src-tauri/src/models.rs create mode 100644 apps/windows-client/src-tauri/src/storage.rs create mode 100644 apps/windows-client/src-tauri/src/validation.rs create mode 100644 apps/windows-client/src-tauri/tauri.conf.json create mode 100644 apps/windows-client/src-tauri/tests/command_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/component_detection_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/domain_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/helper_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs create mode 100644 apps/windows-client/src-tauri/tests/storage_tests.rs create mode 100644 apps/windows-client/src/api/tauriCommands.ts create mode 100644 apps/windows-client/src/app/App.tsx create mode 100644 apps/windows-client/src/domain/types.ts create mode 100644 apps/windows-client/src/main.tsx create mode 100644 apps/windows-client/src/styles/app.css create mode 100644 apps/windows-client/tsconfig.json create mode 100644 apps/windows-client/vite.config.ts create mode 100644 docs/goals/windows-modular-client/EVIDENCE.md create mode 100644 docs/goals/windows-modular-client/GOAL.md create mode 100644 docs/goals/windows-modular-client/PLAN.md create mode 100644 docs/windows-client-product-tech-brief.md diff --git a/README.md b/README.md index 3661c30..51e85a5 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,60 @@ docker compose -f docker-compose.client.yml logs -f docker compose -f docker-compose.client.yml restart ``` +## Windows: standalone desktop client direction + +Windows app routing lives in a separate Tauri 2 desktop utility, not as +`APP_MODE=windows` inside the current Node gateway/client server. The active +workspace slice is `apps/windows-client`. + +Active design documents: + +- Product/tech brief: `docs/windows-client-product-tech-brief.md` +- Execution plan: `docs/goals/windows-modular-client/PLAN.md` +- Windows client README: `apps/windows-client/README.md` + +Target shape: + +- Control App: compact Windows UI for status, profiles, targets, components, + logs, and diagnostics. +- Proxyfier Layer: adapter boundary with ProxiFyre as the first engine for + per-app TCP/UDP routing. +- Local sing-box: optional local runtime; external SOCKS5/HTTP targets must + work without it. + +Development checks: + +```powershell +cd apps/windows-client +npm install +npm run build +npm run tauri -- info + +cd src-tauri +cargo test +``` + +Native Tauri build requires WebView2, Rust/rustup, and Visual Studio Build +Tools with MSVC and Windows SDK components. In the current checkpoint, frontend +builds pass, while native Rust/Tauri tests require that Windows toolchain. + +The three Windows pieces are installed and operated separately: + +```powershell +cd apps/windows-client +& .\scripts\install-control-app.ps1 -PlanOnly +& .\scripts\install-proxyfier.ps1 -PlanOnly +& .\scripts\install-singbox.ps1 -PlanOnly +``` + +`-PlanOnly` returns structured JSON without install side effects. Real install +or service operations must be explicit; profile apply must not silently install +Proxyfier or Local sing-box. + +Windows source configuration is owned by JSON under +`C:\ProgramData\VpnProxy\config`. Generated ProxiFyre and sing-box files under +`C:\ProgramData\VpnProxy\generated` are derived artifacts. + --- # VPN Proxy Gateway diff --git a/apps/windows-client/.gitignore b/apps/windows-client/.gitignore new file mode 100644 index 0000000..cfcc268 --- /dev/null +++ b/apps/windows-client/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +src-tauri/target/ + diff --git a/apps/windows-client/README.md b/apps/windows-client/README.md new file mode 100644 index 0000000..80d1849 --- /dev/null +++ b/apps/windows-client/README.md @@ -0,0 +1,116 @@ +# VPN Proxy Windows Client + +Standalone Windows desktop utility for app-level proxy routing. This app is +separate from the current Docker gateway/client runtime and must not be wired +through `APP_MODE=windows`. + +## Components + +- Control App: Tauri 2 + React/TypeScript UI and Rust command layer. +- Proxyfier Layer: ProxiFyre adapter for per-application routing. +- Local sing-box: optional local runtime, used only by targets that explicitly + require `singbox`. + +External SOCKS5 targets are the MVP path and do not require Local sing-box. + +## Source And Generated Files + +Source configuration is owned by Rust domain models and JSON files under: + +```text +C:\ProgramData\VpnProxy\config\profiles.json +C:\ProgramData\VpnProxy\config\targets.json +C:\ProgramData\VpnProxy\config\components.json +C:\ProgramData\VpnProxy\state\activity.json +``` + +Generated artifacts are derived and can be recreated: + +```text +C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json +C:\ProgramData\VpnProxy\generated\sing-box-config.json +``` + +## Development + +```powershell +cd apps/windows-client +npm install +npm run build +``` + +Run the browser preview shell: + +```powershell +npm run dev -- --host 127.0.0.1 +``` + +Run Tauri checks when the native Windows toolchain is installed: + +```powershell +npm run tauri -- info +npm run tauri -- dev +npm run tauri -- build +``` + +Run Rust tests when Rust/Cargo are installed: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Native Tauri build requires WebView2, Rust via rustup, and Visual Studio Build +Tools with MSVC and Windows SDK components. + +## Explicit Installer Boundaries + +Installer scripts are explicit per component and return structured JSON in +`-PlanOnly` mode: + +```powershell +& .\scripts\install-control-app.ps1 -PlanOnly +& .\scripts\install-proxyfier.ps1 -PlanOnly +& .\scripts\install-singbox.ps1 -PlanOnly +``` + +Installers must be launched intentionally by the user or by a future narrow +helper permission. Profile apply must not silently install Control App, +Proxyfier, or Local sing-box. + +## Existing Proxyfier Detection + +The app detects an already installed Proxyfier layer before showing component +status or applying profiles. Detection checks: + +- uninstall registry entries for `ProxiFyre` and `Proxifier`; +- common install folders such as `C:\Tools\ProxiFyre`, + `%ProgramFiles%\ProxiFyre`, and `%ProgramFiles%\Proxifier`; +- running `ProxiFyre` / `Proxifier` processes and the `ProxiFyreService` + service. + +For portable installs, set an override before launching the app: + +```powershell +$env:VPN_PROXY_PROXIFYRE_ROOT = 'D:\Tools\ProxiFyre' +npm run tauri -- dev +``` + +`ProxiFyre` installs are compatible with the current generated +`app-config.json` apply path. Plain `Proxifier` installs are detected and shown, +but automatic profile apply is not enabled for them yet because they use a +different profile format. + +## MVP Verification Flow + +1. Start the Control App or browser preview. +2. Confirm Components shows Control App, Proxyfier Layer, and optional Local + sing-box separately. +3. Add or keep an external SOCKS5 target. +4. Add a process/folder/exe profile such as Discord. +5. Apply profiles and verify generated ProxiFyre config plus activity entry. +6. Install Proxyfier separately before applying to a real service. +7. Install and start Local sing-box only when using a local target. + +Task evidence is recorded in +`docs/goals/windows-modular-client/EVIDENCE.md`. diff --git a/apps/windows-client/index.html b/apps/windows-client/index.html new file mode 100644 index 0000000..be02298 --- /dev/null +++ b/apps/windows-client/index.html @@ -0,0 +1,12 @@ + + + + + + VPN Proxy для Windows + + +
+ + + diff --git a/apps/windows-client/package-lock.json b/apps/windows-client/package-lock.json new file mode 100644 index 0000000..4dd12ae --- /dev/null +++ b/apps/windows-client/package-lock.json @@ -0,0 +1,2106 @@ +{ + "name": "vpn-proxy-windows-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vpn-proxy-windows-client", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.8.0", + "vite": "^7.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.41", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", + "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.385", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.385.tgz", + "integrity": "sha512-78sa/M08MNAYHQfjoWMvOlKQqZ0ElhSm/L5HNUc96VZ3b+KvDVnngFm8sYQy0XrhTRgAhggHr5abA7yTvRdo4Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": 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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/apps/windows-client/package.json b/apps/windows-client/package.json new file mode 100644 index 0000000..061beac --- /dev/null +++ b/apps/windows-client/package.json @@ -0,0 +1,27 @@ +{ + "name": "vpn-proxy-windows-client", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Standalone Windows desktop proxy management app for VPN Proxy.", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.8.0", + "vite": "^7.0.0" + } +} + diff --git a/apps/windows-client/scripts/install-control-app.ps1 b/apps/windows-client/scripts/install-control-app.ps1 new file mode 100644 index 0000000..bef4573 --- /dev/null +++ b/apps/windows-client/scripts/install-control-app.ps1 @@ -0,0 +1,79 @@ +param( + [string]$InstallRoot = "C:\Program Files\VpnProxy\ControlApp", + [string]$DataRoot = "C:\ProgramData\VpnProxy", + [switch]$PlanOnly, + [switch]$Force +) + +$ErrorActionPreference = "Stop" + +function New-Result { + param( + [bool]$Success, + [string]$Action, + [bool]$Changed, + [string]$Message, + [hashtable]$Details = @{} + ) + + [ordered]@{ + success = $Success + action = $Action + changed = $Changed + message = $Message + details = $Details + } | ConvertTo-Json -Depth 6 +} + +function Test-IsAdministrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Ensure-Directory { + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path)) { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + return $true + } + return $false +} + +try { + $details = @{ + installRoot = $InstallRoot + dataRoot = $DataRoot + planOnly = [bool]$PlanOnly + } + + if ($PlanOnly) { + New-Result -Success $true -Action "install-control-app" -Changed $false -Message "Control App install plan is ready." -Details $details + exit 0 + } + + if (-not (Test-IsAdministrator)) { + New-Result -Success $false -Action "install-control-app" -Changed $false -Message "Administrator rights are required." -Details $details + exit 1 + } + + $changed = $false + $changed = (Ensure-Directory -Path $InstallRoot) -or $changed + $changed = (Ensure-Directory -Path (Join-Path $DataRoot "config")) -or $changed + $changed = (Ensure-Directory -Path (Join-Path $DataRoot "state")) -or $changed + $changed = (Ensure-Directory -Path (Join-Path $DataRoot "generated")) -or $changed + + $markerPath = Join-Path $InstallRoot "install-control-app.marker.json" + if ((-not (Test-Path -LiteralPath $markerPath)) -or $Force) { + @{ component = "control-app"; installedAt = (Get-Date).ToString("o") } | + ConvertTo-Json -Depth 4 | + Set-Content -LiteralPath $markerPath -Encoding UTF8 + $changed = $true + } + + $details.markerPath = $markerPath + New-Result -Success $true -Action "install-control-app" -Changed $changed -Message "Control App directories are installed." -Details $details +} catch { + New-Result -Success $false -Action "install-control-app" -Changed $false -Message $_.Exception.Message + exit 1 +} diff --git a/apps/windows-client/scripts/install-proxyfier.ps1 b/apps/windows-client/scripts/install-proxyfier.ps1 new file mode 100644 index 0000000..521c438 --- /dev/null +++ b/apps/windows-client/scripts/install-proxyfier.ps1 @@ -0,0 +1,96 @@ +param( + [string]$InstallRoot = "C:\Tools\ProxiFyre", + [string]$PackagePath = "", + [string]$ServiceName = "ProxiFyreService", + [switch]$PlanOnly, + [switch]$Force +) + +$ErrorActionPreference = "Stop" + +function New-Result { + param( + [bool]$Success, + [string]$Action, + [bool]$Changed, + [string]$Message, + [hashtable]$Details = @{} + ) + + [ordered]@{ + success = $Success + action = $Action + changed = $Changed + message = $Message + details = $Details + } | ConvertTo-Json -Depth 6 +} + +function Test-IsAdministrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Backup-File { + param([string]$Path) + if (Test-Path -LiteralPath $Path) { + $backup = "$Path.bak" + Copy-Item -LiteralPath $Path -Destination $backup -Force + return $backup + } + return $null +} + +try { + $details = @{ + installRoot = $InstallRoot + packagePath = $PackagePath + serviceName = $ServiceName + planOnly = [bool]$PlanOnly + } + + if ($PlanOnly) { + New-Result -Success $true -Action "install-proxyfier" -Changed $false -Message "Proxyfier install plan is ready." -Details $details + exit 0 + } + + if (-not (Test-IsAdministrator)) { + New-Result -Success $false -Action "install-proxyfier" -Changed $false -Message "Administrator rights are required." -Details $details + exit 1 + } + + if ([string]::IsNullOrWhiteSpace($PackagePath) -or -not (Test-Path -LiteralPath $PackagePath)) { + New-Result -Success $false -Action "install-proxyfier" -Changed $false -Message "PackagePath is required and must point to a local ProxiFyre package." -Details $details + exit 2 + } + + $changed = $false + if (-not (Test-Path -LiteralPath $InstallRoot)) { + New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null + $changed = $true + } + + $configPath = Join-Path $InstallRoot "app-config.json" + $backupPath = Backup-File -Path $configPath + if ($backupPath) { + $details.backupPath = $backupPath + } + + $markerPath = Join-Path $InstallRoot "install-proxyfier.marker.json" + if ((-not (Test-Path -LiteralPath $markerPath)) -or $Force) { + @{ + component = "proxyfier" + packagePath = $PackagePath + serviceName = $ServiceName + installedAt = (Get-Date).ToString("o") + } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $markerPath -Encoding UTF8 + $changed = $true + } + + $details.markerPath = $markerPath + New-Result -Success $true -Action "install-proxyfier" -Changed $changed -Message "Proxyfier install boundary completed." -Details $details +} catch { + New-Result -Success $false -Action "install-proxyfier" -Changed $false -Message $_.Exception.Message + exit 1 +} diff --git a/apps/windows-client/scripts/install-singbox.ps1 b/apps/windows-client/scripts/install-singbox.ps1 new file mode 100644 index 0000000..14c54f8 --- /dev/null +++ b/apps/windows-client/scripts/install-singbox.ps1 @@ -0,0 +1,96 @@ +param( + [string]$InstallRoot = "C:\Program Files\VpnProxy\sing-box", + [string]$BinaryPath = "", + [string]$ServiceName = "VpnProxySingBox", + [switch]$PlanOnly, + [switch]$Force +) + +$ErrorActionPreference = "Stop" + +function New-Result { + param( + [bool]$Success, + [string]$Action, + [bool]$Changed, + [string]$Message, + [hashtable]$Details = @{} + ) + + [ordered]@{ + success = $Success + action = $Action + changed = $Changed + message = $Message + details = $Details + } | ConvertTo-Json -Depth 6 +} + +function Test-IsAdministrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Backup-File { + param([string]$Path) + if (Test-Path -LiteralPath $Path) { + $backup = "$Path.bak" + Copy-Item -LiteralPath $Path -Destination $backup -Force + return $backup + } + return $null +} + +try { + $details = @{ + installRoot = $InstallRoot + binaryPath = $BinaryPath + serviceName = $ServiceName + planOnly = [bool]$PlanOnly + } + + if ($PlanOnly) { + New-Result -Success $true -Action "install-singbox" -Changed $false -Message "Local sing-box install plan is ready." -Details $details + exit 0 + } + + if (-not (Test-IsAdministrator)) { + New-Result -Success $false -Action "install-singbox" -Changed $false -Message "Administrator rights are required." -Details $details + exit 1 + } + + if ([string]::IsNullOrWhiteSpace($BinaryPath) -or -not (Test-Path -LiteralPath $BinaryPath)) { + New-Result -Success $false -Action "install-singbox" -Changed $false -Message "BinaryPath is required and must point to sing-box.exe." -Details $details + exit 2 + } + + $changed = $false + if (-not (Test-Path -LiteralPath $InstallRoot)) { + New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null + $changed = $true + } + + $configPath = Join-Path $InstallRoot "config.json" + $backupPath = Backup-File -Path $configPath + if ($backupPath) { + $details.backupPath = $backupPath + } + + $markerPath = Join-Path $InstallRoot "install-singbox.marker.json" + if ((-not (Test-Path -LiteralPath $markerPath)) -or $Force) { + @{ + component = "singbox" + binaryPath = $BinaryPath + serviceName = $ServiceName + installedAt = (Get-Date).ToString("o") + } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $markerPath -Encoding UTF8 + $changed = $true + } + + $details.markerPath = $markerPath + New-Result -Success $true -Action "install-singbox" -Changed $changed -Message "Local sing-box install boundary completed." -Details $details +} catch { + New-Result -Success $false -Action "install-singbox" -Changed $false -Message $_.Exception.Message + exit 1 +} diff --git a/apps/windows-client/src-tauri/Cargo.lock b/apps/windows-client/src-tauri/Cargo.lock new file mode 100644 index 0000000..2c14ca7 --- /dev/null +++ b/apps/windows-client/src-tauri/Cargo.lock @@ -0,0 +1,4390 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.118", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.2+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.118", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.0", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.0", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.118", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.2+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vpn-proxy-windows-client" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", +] + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/apps/windows-client/src-tauri/Cargo.toml b/apps/windows-client/src-tauri/Cargo.toml new file mode 100644 index 0000000..22e8211 --- /dev/null +++ b/apps/windows-client/src-tauri/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "vpn-proxy-windows-client" +version = "0.1.0" +description = "Standalone Windows desktop proxy management app for VPN Proxy." +authors = ["VPN Proxy"] +edition = "2021" + +[lib] +name = "vpn_proxy_windows_client_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + diff --git a/apps/windows-client/src-tauri/build.rs b/apps/windows-client/src-tauri/build.rs new file mode 100644 index 0000000..137d693 --- /dev/null +++ b/apps/windows-client/src-tauri/build.rs @@ -0,0 +1,4 @@ +fn main() { + tauri_build::build(); +} + diff --git a/apps/windows-client/src-tauri/capabilities/default.json b/apps/windows-client/src-tauri/capabilities/default.json new file mode 100644 index 0000000..b3044eb --- /dev/null +++ b/apps/windows-client/src-tauri/capabilities/default.json @@ -0,0 +1,7 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default capability for the main VPN Proxy Windows shell. Task 8 keeps helper/install launch explicit: no shell or sidecar permission is granted here until a packaged helper is declared.", + "windows": ["main"], + "permissions": ["core:default"] +} diff --git a/apps/windows-client/src-tauri/gen/schemas/acl-manifests.json b/apps/windows-client/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..0eebfc4 --- /dev/null +++ b/apps/windows-client/src-tauri/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-icon-with-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/apps/windows-client/src-tauri/gen/schemas/capabilities.json b/apps/windows-client/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..48b92d1 --- /dev/null +++ b/apps/windows-client/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"default":{"identifier":"default","description":"Default capability for the main VPN Proxy Windows shell. Task 8 keeps helper/install launch explicit: no shell or sidecar permission is granted here until a packaged helper is declared.","local":true,"windows":["main"],"permissions":["core:default"]}} \ No newline at end of file diff --git a/apps/windows-client/src-tauri/gen/schemas/desktop-schema.json b/apps/windows-client/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..3286645 --- /dev/null +++ b/apps/windows-client/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2292 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/apps/windows-client/src-tauri/gen/schemas/windows-schema.json b/apps/windows-client/src-tauri/gen/schemas/windows-schema.json new file mode 100644 index 0000000..3286645 --- /dev/null +++ b/apps/windows-client/src-tauri/gen/schemas/windows-schema.json @@ -0,0 +1,2292 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/apps/windows-client/src-tauri/icons/128x128.png b/apps/windows-client/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..a565b11da00e2192fc53c57f71fda03e24e3ea88 GIT binary patch literal 1726 zcmZuyYfuwc6uz6?BpYaegohXbkszds9ZLD zRir-fQAxETW7WpmDL7VLd?2G8iIo~Hs5{uf2Z#}QXa(A{sZM7!KeE|9=icx8&bQ~D zEl*8JkUA-x5JJ+VM0zg#GyIPf!SzCP=2e75o0905w7h*!ZVc}9jPtmZd5;Y?KVIvS z-k+vQ8bAA5qdCz?#B_u*t`B0lCA8O+tO2j?wj1g7njiazi@Un-4IlWk-@0Jx;>B}@ z@$Dj0I68!>q|_H^HlC_ren${m;>?|kKH1fh9AZY31H?|IzzL=xg-Q6~0kIEPj^@bO zn`AgMT4a_rT5}?_I_xvr#e+&pqmsiZ=hd{Unsvvj!V zMsLG~sdCR$cd13-dmVSN(ddALt05QV{N#v=_Ow&c_={zOU751(qBEI}$h_ZYGRBm~ z*&aU;G?)3Pn5sHvkDl>*SmtM7f(4<=P%KE9E7i69kSvNbKiE+zo1Tise}qlFjwV@T zGdQFnbO{3OT0%kfVMm>pw*1YKH)lG6N_xCdEEoAgKCWlYyzDJA5QgfSy9GS?BG6oCWRv<;p6|8V@R_I3I-5F_0#NYG& zx>Cx-Fy?8EbE%oV?0S>@g|H3hHo9YpLFj7=w~$p5j-Arz=0$C_k}kil5$~7mC#fn- zPbk=KWmzP%p@oRma`RlmtYmv*7Ep!GMH(R=+8TBlLWdz_mSew#85?kQUmDOy52|uE zv%Uor@&b@jXrm!C(vsOJFNy4#4d-bV9h7@P6D z5byJXtLoMx@tn&{uP7O_QlReD0?N)cN7E}<-~12&K1u34`401oX~%qqVn;)Q+CmpJ&|A>7h?WKoIFXeu6rBw zcZCNV3wGRkbgJ!T*{p{P#rE@-s(-GQjL(=IUV4!JA0|k z4xmn=4bKL|{6e?#&uPHV))UVN!=ns4PGxI=ZCBAIP)6%3z|xB$k;ZSOT<;{vzBNw5 zRK~0I5fZ>ziH7B1v0SSASk}E9Wn}Y(yfqp|lp6vXR&}jF@lIT=j&u%TDw<0Ks&c2g za*@7d%fou_-c1vy)gQtFJy>rU=>20a=zV7(isB6Yok0r9h&7Clhl4Q<&dNIjv0<<@ z&hY64H8zyT_o){Z{}+XAr-iLF{xg-($GmDbkwvYj%2lT)ltRNoM{C;RKf#7pJ51rw zX=3i51URspg|<_?*cPiJ9cs3i=7({Oq7k&Lf@$-v7JMioNJ1U+gVO=9H_*})yn6cE b4_yM&VBQt0EebjE)H#f^=1 literal 0 HcmV?d00001 diff --git a/apps/windows-client/src-tauri/icons/128x128@2x.png b/apps/windows-client/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..678a8045a799338088a7fabbb2266a515e87b845 GIT binary patch literal 3213 zcmbVPdsGuw8vkav!XOWmU?D{wA*qT-t*}j%MTEhImk)HSRzY1OS`@LGQ>4uz$Rw>a zR0MbJDn8Ptf}-Ms`Y2YB9oBah!6H<9>N*}=VL>5ByTwJ3y>})myTUnpmVcN#bHCsB z{eIu$-sI0rjPn@mI~V{w65@650#Nvo0>&BtTkzrDQ~>wc3A*Un#+?tYFFWLa;kB5> zwY@uv1b%Kkbx?M1{4b%&wxxx0j&X6kANuvFvUjL6{iUWg&)ikAzY(+6Mtqi|NZTNBNKyDCQKwyx*l&a8SLO@rP!PB1Ir%rE> z-WO4=^CeeL2J@Fc4qTxyZS8z^ZXkEgK3uhgQy&aD771fDb>h*y57dU1>VX9D2#2v6 zQM}e@NRE*LISf@Fwy@LH_V4|-?rWG9;xh(KE&H>6><#AI#EH=(gUxN)?cg3HCE^`- z_O^r&b{9$m;zS75AmQ7jEZcOnDys2%+K6H|Z}aYkDHYx;(fl&2&1-0Mrtb&Pt)yt| znp1FdSMS&zr_JZfD+AN}Pu#{x$oX6(Ws8Pxg=ol@z%Py<@~Gwh64+2~e(ELS_|}g8 z^LvKj(<`t1>317ecmFhY>o)zH^O!A(qid2Y`7AGjS8Z}eukg5qs?tP2yg{C zuF3#VlVUgqhEid+HBNm6AQ2kx!GgD@ zn(vqTeaQ%2_!*Pc7C==_E%vcFD_hNVJV;L!TOijMLWT3{gVkT|B`ZjS| zmm$-swscq}1GX7trg;oElq*k7*ktuwqSUGKI;`a)8ao5$A_eyh$KatOp#@XMuKh%W z9QXB1!K|*1IY)61BU>4cy;$fkXN=PZ0hX6M3CF@?_FpblAya8a9h()TnTY@=XnEl$nx0J&UwsN?iyi7Jn& z=B)=DQ11~EP?JVYpJ_audsA|jfSrXrch;8y4sI#&`%jC7H)M7$`i-P!w&4<1b5^@t zZo1PJd1C2%s4*W3a1q%SJ&!{QT*CSSvWw|GRlvn=5zDvw6 zo@F}B3m24%bTS%3w)S}PaXx+6m242%5$1Rg&K(O`!#FpRoa7=4<36|R+8|MJ{84N` z0+QpwU+^K->yW6~?G+G_1G42(VXGzb$r`#&!99Ap(bhsezFQ?J+2*EJAz^c$uZ~zG z7{{X9i%&6KqtYdI=if+1 zC+q6Ai+zV|-^G)o3rrdoN?~Ux?xpM9-xSRGlv(rDS5nX0(=pm}(4lQsgS z0>*Thz2{*;qjH62w)t+TahH-R4>t^Hkv0+xH3 zmdu!A6^k|H`Qgb2IMovYF|l9$xX2jN3Uobg!F)c<$$lmjC$68%P{*k4I^nY=L*|gg zGkk_S+s+hdAr9xjZG`!iBma}}^02JK;d0`|GYAf&^cLLxvpY literal 0 HcmV?d00001 diff --git a/apps/windows-client/src-tauri/icons/32x32.png b/apps/windows-client/src-tauri/icons/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..87ba0c55e752d1cf292d272a6a0c984ae1faf2ce GIT binary patch literal 940 zcmV;d15^BoP)1YAc(&rswdck~Pod823e$R9l;K*8u4Aqta@o?($4J;Pl0?gjiFJKaR6 z*y)vv+_4ip;!Y&yBiQcLd1`^5%D8j1j*zMD!F?-@Usy*xr0S^atD*#IRvD(5F}GusMPkKBR>!XLqK5$L9#G| zD$G9gjeiuO3o}?`7iKVpnL|Kv3PG|sg(^-z{7Zi?LKmm7$SzJ{ic^Px(gcEJX#!Q6 zeCq%16roEKSY($bFr|q@KzR&7vOI<=k3SXQ6rsywSY(&SFy*mBKxG6$vND3Ij6NN3 zFG5#Fu*j~AU@9Yrfa)^@$?7vy_4!Ky-$&@`Gc2;J&oI?zhk)7;f@Ez7RU3Xe;QI)x zHhg8*hA_3ELqL51L9#x8A{V*q19-?=AHYZc`Tzk6)&~etxIVazs1IP*2Qb&Yy8(?p z1j$Apid^Jw^x+|IqYoeX8+`;Q*ytlfVR}1uqYrc4yBpB#L6B_rpvXn;W)B|nHhb`q zzu7~8g3TU66eiv5-9(t{-ray!7lLG~3q>w+x4Q6Pwxc31CjyL%3qo(ihe7!wHT?U=TAF3J3a7AJNHoj zdiCR$9scM4ywNVUwx7ZFJFxu@Y`+8N-+@p69u+3$^b+n;V@sVa4YoAd(qc=S?RB_I zmn}WE^w~0COUjmvZHfy&Lf-GEbNVZ^&pq1bLi=23p9}4Cp?xm2&xQ86NBdl8p9}4C zp?xm2&xQ86&_2&Dl-o6={~v#g3KMgB33sWnrOuWHTbgWXv8B!SI^3npmL6OBY#Fd6 zWy{Ok%TFMve@1_W_L)cfOlY48?K7c$CbZ9l_LvZckAHrwlPmo8g+Z0WOQz?S9h_dm(`djtEY^jBz~dbCf4_NmZ5 z722ml`&4M33hh&m_NmZ5722ml`&4M3nq@Ek{*?BGknjILX*V&amvEOFTk33Su%*eC z7F*hEuftusZ0WJ3&z1puuq>V5&yc?m)IXuWLi@y{eIm3^g!YNhJ`vg{Lih0xc zaJBvi_oBkYoL<7++{C+I*8bpL%Clt`HekdXS7%7Kl5lm zGa>z7d=wQX=JXQoW|RHP+RNwo;-hpSDurN2V^sYm;%3F-gp!>BMZr+XNm_VPKt`Y>I{ zIK7;^`CsY}X;IN?k42@KRltoLi>qF`-xdc z|5e(n{uBBuw4ZpipO}#TuW2_ir6yLsLESJl3b{zrGC!o-|j!rffw{jajUe2zzV(}j%F%ekA7 z_y6dK{tE3!9_>fws{N~OulkSZuh4$v(SBq?`oE*y#GGEj-CU)A9qr|Fd`G*__CpiW|NXmBVPZ}%;ci0u*V$e^$M^513mK=E zb2m5DzwY*R^#AZqRG664OSqd?dH)oL)&L1)qg;Lh4urF_5%~r{}b&d=JXQo=C}Gc z-CjP&Pqdqi)62P=p#FXOE41%>wC|gj_1{E$)xS@Fh4y`q_I(r5|MQ)wFfpf>a5oqG zZ>qg~j-T(O3mK=Eb2sbkzsdG>^gn(pDoo7jCEShP|4p}-&++)JbRpyPa_%PN{XgEL zze4+-NBf>B|J-Fi^!aS!e7r}0h4wv<_B|8Q{|oIV=JXQo=5=W=Ki^+yHyNjwb2lOV zPu`3Q6LWeA_hec6jr=Fua6G3U8uFMbpMod4FJ@$`1ebC$9Ta_;83{ma^)-mdfKNP{g+wzSyNW_um((q&7J zEq%5O*rN8;_5b?Us4y{SyeysHuc7WV*wSQ6i!E)o*WoT*w)EK2XUl*sDO)nOmvc8E z@Bh~^{gq4F$Fx`I9}Decp?xg0kA?QJNBdZ49}Decp?xg0kA?QJ&_4EPADfW=XSA1= z{dh+AxjI`KY-zHk#g;bP>u{GYTY7Bivt_`Rlr0(C%ekAN{?R4vqojB?qW=ZjM?(8Z zXdemfBcXld(LNH|M?(8ZXdemfBcXjHw2wU6M<%3y*?a%jUy{PqIHk^(23wkJX|biv z_B!09%a$Hn`fM4nC1p#-_HyoK75&d?KUZT*oh=QvG}+Q(OPlR=xJ#EUJ+}1OGGI%} zmW=J?+|70SpAX~W`H=n>XdepgL!o^rv=4>$q0m0`XdepgL!o^rv=4>$q0l}Q+J_$P zLvx+}f4>nICgGGCTk33Su%*eC7F*hEuftusZ0WJ3&z1pOQnqAlFXwJT`nP}Y`F{(x c{VcYh#r8X}{SN#dzXSgN1+d-!f1kjA0dffM7XSbN literal 0 HcmV?d00001 diff --git a/apps/windows-client/src-tauri/src/activity.rs b/apps/windows-client/src-tauri/src/activity.rs new file mode 100644 index 0000000..6c8cc28 --- /dev/null +++ b/apps/windows-client/src-tauri/src/activity.rs @@ -0,0 +1,23 @@ +use crate::models::ActivityEntry; + +pub const DEFAULT_ACTIVITY_LIMIT: usize = 200; + +pub fn sort_activity_desc(mut entries: Vec) -> Vec { + entries.sort_by(|left, right| right.at.cmp(&left.at).then_with(|| right.id.cmp(&left.id))); + entries +} + +pub fn cap_activity(entries: Vec, limit: usize) -> Vec { + let mut entries = sort_activity_desc(entries); + entries.truncate(limit); + entries +} + +pub fn append_activity( + mut entries: Vec, + entry: ActivityEntry, + limit: usize, +) -> Vec { + entries.push(entry); + cap_activity(entries, limit) +} diff --git a/apps/windows-client/src-tauri/src/adapters/proxifyre.rs b/apps/windows-client/src-tauri/src/adapters/proxifyre.rs new file mode 100644 index 0000000..be219e9 --- /dev/null +++ b/apps/windows-client/src-tauri/src/adapters/proxifyre.rs @@ -0,0 +1,242 @@ +use crate::models::{ + ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol, + ProxyProtocol, Target, +}; +#[cfg(test)] +use crate::proxy_router::{ + ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, + ProxyRouterRequest, +}; +#[cfg(not(test))] +use crate::adapters::proxy_router::{ + ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, + ProxyRouterRequest, +}; +use serde::{Deserialize, Serialize}; + +pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre"; +pub const PROXIFYRE_OUTPUT_FILE: &str = "proxifyre-app-config.json"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProxiFyreAdapter { + log_level: String, + bypass_lan: bool, +} + +impl ProxiFyreAdapter { + pub fn new(log_level: impl Into, bypass_lan: bool) -> Self { + Self { + log_level: log_level.into(), + bypass_lan, + } + } + + pub fn generate_proxifyre_config( + &self, + request: ProxyRouterRequest<'_>, + ) -> Result { + let mut proxies = Vec::new(); + + for profile in request.profiles.iter().filter(|profile| profile.enabled) { + let target = find_target(profile, request.targets)?; + ensure_target_supported(profile, target, request.components)?; + + let app_names = app_names_for_profile(profile); + if app_names.is_empty() { + return Err(ProxyRouterError::new( + ProxyRouterErrorKind::EmptyProfileItems, + format!("В профиле '{}' нет приложений для маршрутизации", profile.id), + )); + } + + proxies.push(ProxiFyreProxy { + app_names, + socks5_proxy_endpoint: format!("{}:{}", target.host, target.port), + supported_protocols: protocols_for_profile(profile), + }); + } + + Ok(ProxiFyreConfig { + log_level: self.log_level.clone(), + bypass_lan: self.bypass_lan, + proxies, + }) + } +} + +impl Default for ProxiFyreAdapter { + fn default() -> Self { + Self::new("Info", true) + } +} + +impl ProxyRouterAdapter for ProxiFyreAdapter { + fn id(&self) -> &'static str { + PROXIFYRE_ADAPTER_ID + } + + fn output_file_name(&self) -> &'static str { + PROXIFYRE_OUTPUT_FILE + } + + fn generate_config( + &self, + request: ProxyRouterRequest<'_>, + ) -> Result { + let config = self.generate_proxifyre_config(request)?; + let enabled_profiles = config.proxies.len(); + let routed_apps = config + .proxies + .iter() + .map(|proxy| proxy.app_names.len()) + .sum(); + let contents = serde_json::to_string_pretty(&config).map_err(|error| { + ProxyRouterError::new( + ProxyRouterErrorKind::Serialization, + format!("Не удалось сериализовать конфиг ProxiFyre: {error}"), + ) + })?; + + Ok(ProxyRouterGeneratedConfig { + adapter_id: self.id().to_string(), + output_file_name: self.output_file_name().to_string(), + contents, + enabled_profiles, + routed_apps, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProxiFyreConfig { + #[serde(rename = "logLevel")] + pub log_level: String, + #[serde(rename = "bypassLan")] + pub bypass_lan: bool, + pub proxies: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProxiFyreProxy { + #[serde(rename = "appNames")] + pub app_names: Vec, + #[serde(rename = "socks5ProxyEndpoint")] + pub socks5_proxy_endpoint: String, + #[serde(rename = "supportedProtocols")] + pub supported_protocols: Vec, +} + +fn find_target<'a>( + profile: &Profile, + targets: &'a [Target], +) -> Result<&'a Target, ProxyRouterError> { + targets + .iter() + .find(|target| target.id == profile.target_id) + .ok_or_else(|| { + ProxyRouterError::new( + ProxyRouterErrorKind::MissingTarget, + format!( + "Профиль '{}' ссылается на отсутствующую цель '{}'", + profile.id, profile.target_id + ), + ) + }) +} + +fn ensure_target_supported( + profile: &Profile, + target: &Target, + components: &[ComponentStatus], +) -> Result<(), ProxyRouterError> { + if target.protocol != ProxyProtocol::Socks5 { + return Err(ProxyRouterError::new( + ProxyRouterErrorKind::UnsupportedTargetProtocol, + format!( + "Цель '{}' использует HTTP, но ProxiFyre требует SOCKS5", + target.id + ), + )); + } + + if let Some(required_component) = &target.requires_component { + let Some(status) = components + .iter() + .find(|component| &component.id == required_component) + else { + return Err(ProxyRouterError::new( + ProxyRouterErrorKind::MissingRequiredComponent, + format!( + "Цель '{}' профиля '{}' требует отсутствующий компонент '{}'", + target.id, + profile.id, + component_id_label(required_component) + ), + )); + }; + + if !component_is_running(status) { + return Err(ProxyRouterError::new( + ProxyRouterErrorKind::RequiredComponentNotRunning, + format!( + "Цель '{}' профиля '{}' требует запущенный компонент '{}'", + target.id, + profile.id, + component_id_label(required_component) + ), + )); + } + } + + Ok(()) +} + +fn component_is_running(status: &ComponentStatus) -> bool { + status.installed && status.running && status.state == ComponentState::Running +} + +fn app_names_for_profile(profile: &Profile) -> Vec { + let mut names = Vec::new(); + + for item in &profile.items { + let value = item.value.trim(); + if value.is_empty() { + continue; + } + + let app_name = match item.item_type { + ProfileItemType::Process | ProfileItemType::Folder | ProfileItemType::Exe => value, + }; + + if !names.iter().any(|existing| existing == app_name) { + names.push(app_name.to_string()); + } + } + + names +} + +fn protocols_for_profile(profile: &Profile) -> Vec { + let mut protocols = Vec::new(); + + for protocol in &profile.protocols { + let value = match protocol { + Protocol::Tcp => "TCP", + Protocol::Udp => "UDP", + }; + + if !protocols.iter().any(|existing| existing == value) { + protocols.push(value.to_string()); + } + } + + protocols +} + +fn component_id_label(component_id: &ComponentId) -> &'static str { + match component_id { + ComponentId::ControlApp => "control-app", + ComponentId::Proxyfier => "proxyfier", + ComponentId::Singbox => "singbox", + } +} diff --git a/apps/windows-client/src-tauri/src/adapters/proxy_router.rs b/apps/windows-client/src-tauri/src/adapters/proxy_router.rs new file mode 100644 index 0000000..f9e0162 --- /dev/null +++ b/apps/windows-client/src-tauri/src/adapters/proxy_router.rs @@ -0,0 +1,67 @@ +use crate::models::{ComponentStatus, Profile, Target}; + +#[derive(Debug, Clone, Copy)] +pub struct ProxyRouterRequest<'a> { + pub profiles: &'a [Profile], + pub targets: &'a [Target], + pub components: &'a [ComponentStatus], +} + +impl<'a> ProxyRouterRequest<'a> { + pub fn new( + profiles: &'a [Profile], + targets: &'a [Target], + components: &'a [ComponentStatus], + ) -> Self { + Self { + profiles, + targets, + components, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProxyRouterGeneratedConfig { + pub adapter_id: String, + pub output_file_name: String, + pub contents: String, + pub enabled_profiles: usize, + pub routed_apps: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProxyRouterError { + pub kind: ProxyRouterErrorKind, + pub message: String, +} + +impl ProxyRouterError { + pub fn new(kind: ProxyRouterErrorKind, message: impl Into) -> Self { + Self { + kind, + message: message.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProxyRouterErrorKind { + EmptyProfileItems, + MissingTarget, + MissingRequiredComponent, + RequiredComponentNotRunning, + UnsupportedTargetProtocol, + Serialization, +} + +pub trait ProxyRouterAdapter { + fn id(&self) -> &'static str; + + fn output_file_name(&self) -> &'static str; + + fn generate_config( + &self, + request: ProxyRouterRequest<'_>, + ) -> Result; +} diff --git a/apps/windows-client/src-tauri/src/adapters/singbox.rs b/apps/windows-client/src-tauri/src/adapters/singbox.rs new file mode 100644 index 0000000..bc4f08a --- /dev/null +++ b/apps/windows-client/src-tauri/src/adapters/singbox.rs @@ -0,0 +1,358 @@ +use crate::models::{ + ComponentId, ComponentState, ComponentStatus, ProxyProtocol, Target, TargetKind, +}; +use serde::{Deserialize, Serialize}; +use std::{ + env, fs, + path::Path, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; + +pub const SINGBOX_ADAPTER_ID: &str = "singbox"; +pub const SINGBOX_OUTPUT_FILE: &str = "sing-box-config.json"; +pub const DEFAULT_MIXED_INBOUND_TAG: &str = "vpn-proxy-mixed-in"; +pub const DEFAULT_DIRECT_OUTBOUND_TAG: &str = "direct"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SingBoxAdapter { + log_level: String, + inbound_tag: String, + outbound_tag: String, +} + +impl SingBoxAdapter { + pub fn new( + log_level: impl Into, + inbound_tag: impl Into, + outbound_tag: impl Into, + ) -> Self { + Self { + log_level: log_level.into(), + inbound_tag: inbound_tag.into(), + outbound_tag: outbound_tag.into(), + } + } + + pub fn generate_config( + &self, + request: SingBoxGenerationRequest<'_>, + checker: &C, + ) -> Result + where + C: SingBoxConfigChecker, + { + let target = find_local_singbox_target(request.targets)?; + ensure_local_singbox_target(target, request.components)?; + + let config = SingBoxConfig { + log: SingBoxLog { + disabled: false, + level: self.log_level.clone(), + timestamp: true, + }, + inbounds: vec![SingBoxInbound { + inbound_type: "mixed".to_string(), + tag: self.inbound_tag.clone(), + listen: target.host.clone(), + listen_port: target.port, + users: Vec::new(), + set_system_proxy: false, + }], + outbounds: vec![SingBoxOutbound { + outbound_type: "direct".to_string(), + tag: self.outbound_tag.clone(), + }], + route: SingBoxRoute { + final_outbound: self.outbound_tag.clone(), + }, + }; + let contents = serde_json::to_string_pretty(&config).map_err(|error| { + SingBoxConfigError::new( + SingBoxConfigErrorKind::Serialization, + format!("Не удалось сериализовать конфиг sing-box: {error}"), + ) + })?; + let check = match request.binary_path { + Some(binary_path) => Some(checker.check_config(binary_path, &contents)?), + None => None, + }; + + Ok(SingBoxGeneratedConfig { + adapter_id: SINGBOX_ADAPTER_ID.to_string(), + output_file_name: SINGBOX_OUTPUT_FILE.to_string(), + contents, + local_target_id: target.id.clone(), + listen: target.host.clone(), + listen_port: target.port, + check, + }) + } +} + +impl Default for SingBoxAdapter { + fn default() -> Self { + Self::new( + "info", + DEFAULT_MIXED_INBOUND_TAG, + DEFAULT_DIRECT_OUTBOUND_TAG, + ) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SingBoxGenerationRequest<'a> { + pub targets: &'a [Target], + pub components: &'a [ComponentStatus], + pub binary_path: Option<&'a Path>, +} + +impl<'a> SingBoxGenerationRequest<'a> { + pub fn new( + targets: &'a [Target], + components: &'a [ComponentStatus], + binary_path: Option<&'a Path>, + ) -> Self { + Self { + targets, + components, + binary_path, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SingBoxGeneratedConfig { + pub adapter_id: String, + pub output_file_name: String, + pub contents: String, + pub local_target_id: String, + pub listen: String, + pub listen_port: u16, + pub check: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxCheckResult { + pub checked: bool, + pub success: bool, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SingBoxConfigError { + pub kind: SingBoxConfigErrorKind, + pub message: String, +} + +impl SingBoxConfigError { + pub fn new(kind: SingBoxConfigErrorKind, message: impl Into) -> Self { + Self { + kind, + message: message.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SingBoxConfigErrorKind { + MissingLocalTarget, + MissingRequiredComponent, + RequiredComponentNotRunning, + UnsupportedTarget, + Serialization, + CheckFailed, +} + +pub trait SingBoxConfigChecker { + fn check_config( + &self, + binary_path: &Path, + config_json: &str, + ) -> Result; +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct SingBoxCommandChecker; + +impl SingBoxConfigChecker for SingBoxCommandChecker { + fn check_config( + &self, + binary_path: &Path, + config_json: &str, + ) -> Result { + let config_path = env::temp_dir().join(format!( + "vpn-proxy-sing-box-{}-{}.json", + std::process::id(), + now_millis() + )); + + fs::write(&config_path, config_json).map_err(|error| { + SingBoxConfigError::new( + SingBoxConfigErrorKind::CheckFailed, + format!( + "Не удалось записать временный конфиг sing-box '{}': {error}", + config_path.display() + ), + ) + })?; + + let output = Command::new(binary_path) + .arg("check") + .arg("-c") + .arg(&config_path) + .output() + .map_err(|error| { + let _ = fs::remove_file(&config_path); + SingBoxConfigError::new( + SingBoxConfigErrorKind::CheckFailed, + format!("Не удалось выполнить '{} check': {error}", binary_path.display()), + ) + })?; + let _ = fs::remove_file(&config_path); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let message = command_message(&stdout, &stderr); + + if !output.status.success() { + return Err(SingBoxConfigError::new( + SingBoxConfigErrorKind::CheckFailed, + format!("Проверка sing-box не прошла: {message}"), + )); + } + + Ok(SingBoxCheckResult { + checked: true, + success: true, + message: if message.is_empty() { + "Проверка sing-box прошла успешно".to_string() + } else { + message + }, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxConfig { + pub log: SingBoxLog, + pub inbounds: Vec, + pub outbounds: Vec, + pub route: SingBoxRoute, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxLog { + pub disabled: bool, + pub level: String, + pub timestamp: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxInbound { + #[serde(rename = "type")] + pub inbound_type: String, + pub tag: String, + pub listen: String, + #[serde(rename = "listen_port")] + pub listen_port: u16, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub users: Vec, + #[serde(rename = "set_system_proxy")] + pub set_system_proxy: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxUser { + pub username: String, + pub password: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxOutbound { + #[serde(rename = "type")] + pub outbound_type: String, + pub tag: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SingBoxRoute { + #[serde(rename = "final")] + pub final_outbound: String, +} + +fn find_local_singbox_target(targets: &[Target]) -> Result<&Target, SingBoxConfigError> { + targets + .iter() + .find(|target| { + target.kind == TargetKind::Local + && target.requires_component.as_ref() == Some(&ComponentId::Singbox) + }) + .ok_or_else(|| { + SingBoxConfigError::new( + SingBoxConfigErrorKind::MissingLocalTarget, + "Локальная цель, требующая sing-box, не настроена", + ) + }) +} + +fn ensure_local_singbox_target( + target: &Target, + components: &[ComponentStatus], +) -> Result<(), SingBoxConfigError> { + if target.kind != TargetKind::Local + || target.protocol != ProxyProtocol::Socks5 + || target.requires_component.as_ref() != Some(&ComponentId::Singbox) + { + return Err(SingBoxConfigError::new( + SingBoxConfigErrorKind::UnsupportedTarget, + format!( + "Цель '{}' должна быть локальной SOCKS5-целью, требующей sing-box", + target.id + ), + )); + } + + let Some(status) = components + .iter() + .find(|component| component.id == ComponentId::Singbox) + else { + return Err(SingBoxConfigError::new( + SingBoxConfigErrorKind::MissingRequiredComponent, + format!("Локальная цель '{}' требует состояние компонента sing-box", target.id), + )); + }; + + if !component_is_running(status) { + return Err(SingBoxConfigError::new( + SingBoxConfigErrorKind::RequiredComponentNotRunning, + format!("Локальная цель '{}' требует установленный и запущенный sing-box", target.id), + )); + } + + Ok(()) +} + +fn component_is_running(status: &ComponentStatus) -> bool { + status.installed && status.running && status.state == ComponentState::Running +} + +fn now_millis() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_millis()) + .unwrap_or_default() +} + +fn command_message(stdout: &str, stderr: &str) -> String { + let stdout = stdout.trim(); + let stderr = stderr.trim(); + + match (stdout.is_empty(), stderr.is_empty()) { + (true, true) => String::new(), + (false, true) => stdout.to_string(), + (true, false) => stderr.to_string(), + (false, false) => format!("{stdout}\n{stderr}"), + } +} diff --git a/apps/windows-client/src-tauri/src/commands.rs b/apps/windows-client/src-tauri/src/commands.rs new file mode 100644 index 0000000..cfe4f86 --- /dev/null +++ b/apps/windows-client/src-tauri/src/commands.rs @@ -0,0 +1,971 @@ +#[cfg(not(test))] +use crate::adapters::proxifyre::ProxiFyreAdapter; +#[cfg(not(test))] +use crate::adapters::proxy_router::{ + ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, + ProxyRouterRequest, +}; +#[cfg(test)] +use crate::proxifyre::ProxiFyreAdapter; +#[cfg(test)] +use crate::proxy_router::{ + ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, + ProxyRouterRequest, +}; +use crate::component_detection::{ + detect_proxyfier_install, detect_proxyfier_install_with_host, + proxyfier_component_from_detection, DetectedProxyfier, ProxyfierDetectionHost, + SystemProxyfierDetectionHost, +}; +use crate::models::{ + ActivityEntry, ActivityLevel, ComponentId, ComponentState, ComponentStatus, Profile, + ProfileInput, ProfileItem, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol, Target, + TargetInput, TargetKind, +}; +use crate::storage::{default_config_root, JsonStorage}; +use crate::validation::{normalize_profile, normalize_target, ValidationError}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Debug, Clone)] +pub struct CommandState { + root: PathBuf, +} + +impl CommandState { + pub fn new(root: impl Into) -> Self { + Self { root: root.into() } + } + + pub fn storage(&self) -> JsonStorage { + JsonStorage::new(self.root.clone()) + } +} + +impl Default for CommandState { + fn default() -> Self { + Self::new(default_config_root()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandError { + pub code: String, + pub message: String, + #[serde(default)] + pub details: Vec, +} + +impl CommandError { + fn new(code: impl Into, message: impl Into) -> Self { + Self { + code: code.into(), + message: message.into(), + details: Vec::new(), + } + } + + fn with_details( + code: impl Into, + message: impl Into, + details: Vec, + ) -> Self { + Self { + code: code.into(), + message: message.into(), + details, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ValidationIssue { + pub field: String, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StatusResponse { + pub route_line: String, + pub active_profile_count: usize, + pub routed_app_count: usize, + pub active_target: Option, + pub components: Vec, + pub recent_activity: Vec, + pub generated_config_path: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProfileInputDto { + pub id: Option, + pub name: String, + #[serde(default)] + pub enabled: Option, + #[serde(default)] + pub target_id: Option, + #[serde(default)] + pub protocols: Option>, + #[serde(default)] + pub items: Option>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProfileItemInputDto { + #[serde(rename = "type")] + pub item_type: String, + pub value: String, + #[serde(default)] + pub recursive: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TargetInputDto { + pub id: Option, + pub name: String, + #[serde(default)] + pub kind: Option, + #[serde(default)] + pub protocol: Option, + pub host: String, + pub port: u32, + #[serde(default)] + pub requires_component: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProfileDto { + pub id: String, + pub name: String, + pub enabled: bool, + pub target_id: String, + pub protocols: Vec, + pub items: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProfileItemDto { + #[serde(rename = "type")] + pub item_type: ProfileItemType, + pub value: String, + pub recursive: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TargetDto { + pub id: String, + pub name: String, + pub kind: TargetKind, + pub protocol: ProxyProtocol, + pub host: String, + pub port: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub requires_component: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComponentStatusDto { + pub id: ComponentId, + pub name: String, + pub state: ComponentState, + pub installed: bool, + pub running: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + pub problems: Vec, + pub actions: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ActivityEntryDto { + pub id: String, + pub at: String, + pub level: ActivityLevel, + pub title: String, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResolveProfilePreviewResponse { + pub profile_id: String, + pub apps: Vec, + pub warnings: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResolvedAppDto { + pub source_type: ProfileItemType, + pub source_value: String, + pub app_name: String, + pub notes: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplyProfilesResponse { + pub success: bool, + pub changed: bool, + pub message: String, + pub adapter_id: String, + pub generated_config_path: String, + pub enabled_profiles: usize, + pub routed_apps: usize, + pub helper: HelperApplyResult, + pub activity: ActivityEntryDto, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelperApplyResult { + pub success: bool, + pub changed: bool, + pub action: String, + pub message: String, +} + +pub struct HelperApplyRequest<'a> { + pub adapter_id: &'a str, + pub config_path: &'a Path, + pub config_contents: &'a str, +} + +pub trait ProxyApplyHelper { + fn apply_proxy_config( + &self, + request: HelperApplyRequest<'_>, + ) -> Result; +} + +pub trait Clock { + fn now(&self) -> String; +} + +pub struct SystemClock; + +impl Clock for SystemClock { + fn now(&self) -> String { + let seconds = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_secs()) + .unwrap_or(0); + format!("unix:{seconds}") + } +} + +pub struct StagedApplyHelper; + +impl ProxyApplyHelper for StagedApplyHelper { + fn apply_proxy_config( + &self, + request: HelperApplyRequest<'_>, + ) -> Result { + Ok(HelperApplyResult { + success: true, + changed: true, + action: format!("{}.stage-generated-config", request.adapter_id), + message: format!( + "Сгенерированный конфиг подготовлен в {}; интеграция привилегированного помощника еще не подключена", + request.config_path.display() + ), + }) + } +} + +pub struct DetectedProxyApplyHelper { + host: H, +} + +impl DetectedProxyApplyHelper { + pub fn system() -> Self { + Self { + host: SystemProxyfierDetectionHost, + } + } +} + +impl DetectedProxyApplyHelper { + pub fn new(host: H) -> Self { + Self { host } + } +} + +impl ProxyApplyHelper for DetectedProxyApplyHelper +where + H: ProxyfierDetectionHost, +{ + fn apply_proxy_config( + &self, + request: HelperApplyRequest<'_>, + ) -> Result { + let Some(detected) = detect_proxyfier_install_with_host(&self.host) else { + return staged_apply_result(request); + }; + + apply_to_detected_proxyfier(request, &detected) + } +} + +#[tauri::command] +pub fn get_status(state: tauri::State<'_, CommandState>) -> Result { + build_status(&state.storage()) +} + +#[tauri::command] +pub fn get_profiles( + state: tauri::State<'_, CommandState>, +) -> Result, CommandError> { + read_profiles(&state.storage()) +} + +#[tauri::command] +pub fn save_profile( + state: tauri::State<'_, CommandState>, + input: ProfileInputDto, +) -> Result { + save_profile_to_storage(&state.storage(), input) +} + +#[tauri::command] +pub fn get_targets(state: tauri::State<'_, CommandState>) -> Result, CommandError> { + read_targets(&state.storage()) +} + +#[tauri::command] +pub fn save_target( + state: tauri::State<'_, CommandState>, + input: TargetInputDto, +) -> Result { + save_target_to_storage(&state.storage(), input) +} + +#[tauri::command] +pub fn get_components( + state: tauri::State<'_, CommandState>, +) -> Result, CommandError> { + read_components(&state.storage()) +} + +#[tauri::command] +pub fn resolve_profile_preview( + input: ProfileInputDto, +) -> Result { + resolve_preview(input) +} + +#[tauri::command] +pub fn apply_profiles( + state: tauri::State<'_, CommandState>, +) -> Result { + let storage = state.storage(); + let adapter = ProxiFyreAdapter::default(); + let helper = DetectedProxyApplyHelper::system(); + let clock = SystemClock; + + apply_profiles_with_services(&storage, &adapter, &helper, &clock) +} + +#[tauri::command] +pub fn get_logs( + state: tauri::State<'_, CommandState>, +) -> Result, CommandError> { + read_activity(&state.storage()) +} + +#[tauri::command] +pub fn open_config_location(state: tauri::State<'_, CommandState>) -> Result { + let storage = state.storage(); + let generated_path = storage.paths().generated_dir.join("proxifyre-app-config.json"); + let config_path = detect_proxyfier_install() + .and_then(|detected| detected.config_path) + .filter(|path| path.exists()) + .or_else(|| generated_path.exists().then_some(generated_path.clone())); + + let Some(config_path) = config_path else { + return open_folder(&storage.paths().generated_dir); + }; + + open_file_or_select(&config_path)?; + Ok(config_path.display().to_string()) +} + +pub fn build_status(storage: &JsonStorage) -> Result { + let profiles = storage.read_profiles().map_err(storage_error)?; + let targets = storage.read_targets().map_err(storage_error)?; + let components = components_or_defaults(storage)?; + let activity = storage.read_activity().map_err(storage_error)?; + let active_profile_count = profiles.iter().filter(|profile| profile.enabled).count(); + let routed_app_count = profiles + .iter() + .filter(|profile| profile.enabled) + .map(|profile| profile.items.len()) + .sum(); + let active_target = profiles + .iter() + .find(|profile| profile.enabled) + .and_then(|profile| targets.iter().find(|target| target.id == profile.target_id)); + let route_line = route_line(active_target); + + Ok(StatusResponse { + route_line, + active_profile_count, + routed_app_count, + active_target: active_target.map(TargetDto::from), + components: components.iter().map(ComponentStatusDto::from).collect(), + recent_activity: activity.iter().take(10).map(ActivityEntryDto::from).collect(), + generated_config_path: storage + .paths() + .generated_dir + .join("proxifyre-app-config.json") + .display() + .to_string(), + }) +} + +pub fn read_profiles(storage: &JsonStorage) -> Result, CommandError> { + storage + .read_profiles() + .map_err(storage_error) + .map(|profiles| profiles.iter().map(ProfileDto::from).collect()) +} + +pub fn save_profile_to_storage( + storage: &JsonStorage, + input: ProfileInputDto, +) -> Result { + let profile = normalize_profile(input.into()).map_err(validation_error)?; + let mut profiles = storage.read_profiles().map_err(storage_error)?; + + match profiles + .iter() + .position(|existing| existing.id == profile.id) + { + Some(index) => profiles[index] = profile.clone(), + None => profiles.push(profile.clone()), + } + + storage.write_profiles(&profiles).map_err(storage_error)?; + Ok(ProfileDto::from(&profile)) +} + +pub fn read_targets(storage: &JsonStorage) -> Result, CommandError> { + storage + .read_targets() + .map_err(storage_error) + .map(|targets| targets.iter().map(TargetDto::from).collect()) +} + +pub fn save_target_to_storage( + storage: &JsonStorage, + input: TargetInputDto, +) -> Result { + let target = normalize_target(input.into()).map_err(validation_error)?; + let mut targets = storage.read_targets().map_err(storage_error)?; + + match targets.iter().position(|existing| existing.id == target.id) { + Some(index) => targets[index] = target.clone(), + None => targets.push(target.clone()), + } + + storage.write_targets(&targets).map_err(storage_error)?; + Ok(TargetDto::from(&target)) +} + +pub fn read_components(storage: &JsonStorage) -> Result, CommandError> { + components_or_defaults(storage).map(|components| { + components + .iter() + .map(ComponentStatusDto::from) + .collect::>() + }) +} + +pub fn read_activity(storage: &JsonStorage) -> Result, CommandError> { + storage + .read_activity() + .map_err(storage_error) + .map(|entries| entries.iter().map(ActivityEntryDto::from).collect()) +} + +pub fn resolve_preview( + input: ProfileInputDto, +) -> Result { + let profile = normalize_profile(input.into()).map_err(validation_error)?; + let mut warnings = Vec::new(); + let apps = profile + .items + .iter() + .map(|item| resolved_app(item, &mut warnings)) + .collect(); + + Ok(ResolveProfilePreviewResponse { + profile_id: profile.id, + apps, + warnings, + }) +} + +pub fn apply_profiles_with_services( + storage: &JsonStorage, + adapter: &impl ProxyRouterAdapter, + helper: &impl ProxyApplyHelper, + clock: &impl Clock, +) -> Result { + let profiles = storage.read_profiles().map_err(storage_error)?; + let targets = storage.read_targets().map_err(storage_error)?; + let components = components_or_defaults(storage)?; + let generated = + match adapter.generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) { + Ok(generated) => generated, + Err(error) => { + let command_error = adapter_error(error); + let activity = activity_for_apply_error(clock, &command_error); + storage.append_activity(activity).map_err(storage_error)?; + return Err(command_error); + } + }; + + let generated_path = storage + .paths() + .generated_dir + .join(generated.output_file_name.as_str()); + write_generated_config(&generated_path, &generated.contents)?; + + let helper_result = helper.apply_proxy_config(HelperApplyRequest { + adapter_id: generated.adapter_id.as_str(), + config_path: &generated_path, + config_contents: generated.contents.as_str(), + })?; + + let activity = activity_for_apply(clock, &generated, &generated_path, &helper_result); + storage + .append_activity(activity.clone()) + .map_err(storage_error)?; + + Ok(ApplyProfilesResponse { + success: helper_result.success, + changed: helper_result.changed, + message: helper_result.message.clone(), + adapter_id: generated.adapter_id, + generated_config_path: generated_path.display().to_string(), + enabled_profiles: generated.enabled_profiles, + routed_apps: generated.routed_apps, + helper: helper_result, + activity: ActivityEntryDto::from(&activity), + }) +} + +fn components_or_defaults(storage: &JsonStorage) -> Result, CommandError> { + let components = storage.read_components().map_err(storage_error)?; + Ok(resolve_component_statuses( + components, + detect_proxyfier_install(), + )) +} + +pub fn resolve_component_statuses( + stored_components: Vec, + detected_proxyfier: Option, +) -> Vec { + let mut components = default_components(); + + for component in stored_components { + upsert_component(&mut components, component); + } + + if detected_proxyfier.is_some() { + upsert_component( + &mut components, + proxyfier_component_from_detection(detected_proxyfier.as_ref()), + ); + } + + components +} + +fn default_components() -> Vec { + vec![ + ComponentStatus { + id: ComponentId::ControlApp, + name: "Приложение управления".to_string(), + state: ComponentState::Running, + installed: true, + running: true, + version: None, + path: None, + problems: Vec::new(), + actions: vec!["Открыть журнал".to_string(), "Скопировать диагностику".to_string()], + }, + ComponentStatus { + id: ComponentId::Proxyfier, + name: "ProxiFyre".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["ProxiFyre нужен для маршрутизации выбранных приложений".to_string()], + actions: vec!["Установить ProxiFyre".to_string()], + }, + ComponentStatus { + id: ComponentId::Singbox, + name: "Локальный sing-box".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: Vec::new(), + actions: vec!["Установить локальный sing-box".to_string()], + }, + ] +} + +fn upsert_component(components: &mut Vec, component: ComponentStatus) { + match components + .iter() + .position(|existing| existing.id == component.id) + { + Some(index) => components[index] = component, + None => components.push(component), + } +} + +fn route_line(active_target: Option<&Target>) -> String { + match active_target { + Some(target) if target.id == "local-singbox" => { + format!( + "Выбранные приложения -> ProxiFyre -> локальный sing-box {}:{} -> VPN", + target.host, target.port + ) + } + Some(target) => format!( + "Выбранные приложения -> ProxiFyre -> внешний прокси {}:{}", + target.host, target.port + ), + None => "Выбранные приложения -> ProxiFyre -> внешний прокси".to_string(), + } +} + +fn resolved_app(item: &ProfileItem, warnings: &mut Vec) -> ResolvedAppDto { + let mut notes = Vec::new(); + match item.item_type { + ProfileItemType::Process => notes.push("Имя процесса используется напрямую".to_string()), + ProfileItemType::Folder => { + let note = "Сканирование папок отложено; ProxiFyre получает путь к папке"; + notes.push(note.to_string()); + warnings.push(note.to_string()); + } + ProfileItemType::Exe => { + notes.push("Путь к EXE сохраняется для сопоставления в ProxiFyre".to_string()) + } + } + + ResolvedAppDto { + source_type: item.item_type.clone(), + source_value: item.value.clone(), + app_name: item.value.clone(), + notes, + } +} + +fn write_generated_config(path: &Path, contents: &str) -> Result<(), CommandError> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).map_err(storage_error)?; + } + fs::write(path, contents).map_err(storage_error) +} + +fn open_file_or_select(path: &Path) -> Result<(), CommandError> { + let status = Command::new("notepad.exe") + .arg(path) + .spawn() + .map_err(|error| { + CommandError::new( + "open_config_failed", + format!("Не удалось открыть конфиг '{}': {error}", path.display()), + ) + })?; + + drop(status); + Ok(()) +} + +fn open_folder(path: &Path) -> Result { + fs::create_dir_all(path).map_err(storage_error)?; + Command::new("explorer.exe") + .arg(path) + .spawn() + .map_err(|error| { + CommandError::new( + "open_config_failed", + format!("Не удалось открыть папку '{}': {error}", path.display()), + ) + })?; + + Ok(path.display().to_string()) +} + +fn apply_to_detected_proxyfier( + request: HelperApplyRequest<'_>, + detected: &DetectedProxyfier, +) -> Result { + let Some(config_path) = &detected.config_path else { + return staged_apply_result(request); + }; + + if let Some(parent) = config_path.parent() { + fs::create_dir_all(parent).map_err(|error| { + CommandError::new( + "proxyfier_apply_failed", + format!( + "Не удалось создать папку конфига ProxiFyre '{}': {error}", + parent.display() + ), + ) + })?; + } + + if config_path.exists() { + let backup_path = config_path.with_file_name(format!( + "{}.bak", + config_path + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("app-config.json") + )); + fs::copy(config_path, backup_path).map_err(|error| { + CommandError::new( + "proxyfier_apply_failed", + format!( + "Не удалось создать backup текущего конфига ProxiFyre '{}': {error}", + config_path.display() + ), + ) + })?; + } + + fs::write(config_path, request.config_contents).map_err(|error| { + CommandError::new( + "proxyfier_apply_failed", + format!( + "Не удалось записать конфиг ProxiFyre '{}': {error}", + config_path.display() + ), + ) + })?; + + Ok(HelperApplyResult { + success: true, + changed: true, + action: "proxifyre.apply-detected-config".to_string(), + message: format!( + "Сгенерированный конфиг записан в найденную установку ProxiFyre: {}", + config_path.display() + ), + }) +} + +fn staged_apply_result(request: HelperApplyRequest<'_>) -> Result { + Ok(HelperApplyResult { + success: true, + changed: true, + action: format!("{}.stage-generated-config", request.adapter_id), + message: format!( + "Сгенерированный конфиг подготовлен в {}; совместимая установка ProxiFyre не найдена", + request.config_path.display() + ), + }) +} + +fn activity_for_apply( + clock: &impl Clock, + generated: &ProxyRouterGeneratedConfig, + generated_path: &Path, + helper_result: &HelperApplyResult, +) -> ActivityEntry { + let level = if helper_result.success { + ActivityLevel::Success + } else { + ActivityLevel::Error + }; + + ActivityEntry { + id: format!("apply-{}", generated.adapter_id), + at: clock.now(), + level, + title: "Конфиг ProxiFyre создан".to_string(), + message: format!( + "Профилей: {}, приложений: {}, конфиг: {}", + generated.enabled_profiles, + generated.routed_apps, + generated_path.display() + ), + } +} + +fn activity_for_apply_error(clock: &impl Clock, error: &CommandError) -> ActivityEntry { + ActivityEntry { + id: format!("apply-error-{}", error.code), + at: clock.now(), + level: ActivityLevel::Error, + title: "Применение ProxiFyre заблокировано".to_string(), + message: error.message.clone(), + } +} + +fn storage_error(error: std::io::Error) -> CommandError { + CommandError::new("storage_error", error.to_string()) +} + +fn validation_error(errors: Vec) -> CommandError { + CommandError::with_details( + "validation_error", + "Проверка введенных данных не прошла", + errors + .into_iter() + .map(|error| ValidationIssue { + field: error.field, + message: error.message, + }) + .collect(), + ) +} + +fn adapter_error(error: ProxyRouterError) -> CommandError { + let code = match error.kind { + ProxyRouterErrorKind::EmptyProfileItems => "empty_profile_items", + ProxyRouterErrorKind::MissingTarget => "missing_target", + ProxyRouterErrorKind::MissingRequiredComponent => "missing_required_component", + ProxyRouterErrorKind::RequiredComponentNotRunning => "required_component_not_running", + ProxyRouterErrorKind::UnsupportedTargetProtocol => "unsupported_target_protocol", + ProxyRouterErrorKind::Serialization => "serialization_error", + }; + + CommandError::new(code, error.message) +} + +impl From for ProfileInput { + fn from(input: ProfileInputDto) -> Self { + Self { + id: input.id, + name: input.name, + enabled: input.enabled.unwrap_or(true), + target_id: input + .target_id + .unwrap_or_else(|| "local-singbox".to_string()), + protocols: input + .protocols + .unwrap_or_else(|| vec!["TCP".to_string(), "UDP".to_string()]), + items: input + .items + .unwrap_or_default() + .into_iter() + .map(ProfileItemInput::from) + .collect(), + } + } +} + +impl From for ProfileItemInput { + fn from(input: ProfileItemInputDto) -> Self { + Self { + item_type: input.item_type, + value: input.value, + recursive: input.recursive, + } + } +} + +impl From for TargetInput { + fn from(input: TargetInputDto) -> Self { + Self { + id: input.id, + name: input.name, + kind: input.kind.unwrap_or_else(|| "external".to_string()), + protocol: input.protocol.unwrap_or_else(|| "socks5".to_string()), + host: input.host, + port: input.port, + requires_component: input.requires_component, + } + } +} + +impl From<&Profile> for ProfileDto { + fn from(profile: &Profile) -> Self { + Self { + id: profile.id.clone(), + name: profile.name.clone(), + enabled: profile.enabled, + target_id: profile.target_id.clone(), + protocols: profile.protocols.clone(), + items: profile.items.iter().map(ProfileItemDto::from).collect(), + } + } +} + +impl From<&ProfileItem> for ProfileItemDto { + fn from(item: &ProfileItem) -> Self { + Self { + item_type: item.item_type.clone(), + value: item.value.clone(), + recursive: item.recursive, + } + } +} + +impl From<&Target> for TargetDto { + fn from(target: &Target) -> Self { + Self { + id: target.id.clone(), + name: target.name.clone(), + kind: target.kind.clone(), + protocol: target.protocol.clone(), + host: target.host.clone(), + port: target.port, + requires_component: target.requires_component.clone(), + } + } +} + +impl From<&ComponentStatus> for ComponentStatusDto { + fn from(component: &ComponentStatus) -> Self { + Self { + id: component.id.clone(), + name: component.name.clone(), + state: component.state.clone(), + installed: component.installed, + running: component.running, + version: component.version.clone(), + path: component.path.clone(), + problems: component.problems.clone(), + actions: component.actions.clone(), + } + } +} + +impl From<&ActivityEntry> for ActivityEntryDto { + fn from(entry: &ActivityEntry) -> Self { + Self { + id: entry.id.clone(), + at: entry.at.clone(), + level: entry.level.clone(), + title: entry.title.clone(), + message: entry.message.clone(), + } + } +} diff --git a/apps/windows-client/src-tauri/src/component_detection.rs b/apps/windows-client/src-tauri/src/component_detection.rs new file mode 100644 index 0000000..c964735 --- /dev/null +++ b/apps/windows-client/src-tauri/src/component_detection.rs @@ -0,0 +1,414 @@ +use crate::models::{ComponentId, ComponentState, ComponentStatus}; +use serde::Deserialize; +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProxyfierEngine { + ProxiFyre, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DetectedProxyfier { + pub engine: ProxyfierEngine, + pub name: String, + pub install_dir: PathBuf, + pub executable_path: PathBuf, + pub config_path: Option, + pub running: bool, + pub service_name: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RegistryInstallEntry { + pub display_name: String, + pub install_location: Option, + pub display_icon: Option, +} + +pub trait ProxyfierDetectionHost { + fn env_var(&self, name: &str) -> Option; + + fn path_exists(&self, path: &Path) -> bool; + + fn process_running(&self, process_name: &str) -> bool; + + fn service_running(&self, service_name: &str) -> bool; + + fn registry_install_entries(&self) -> Vec; +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct SystemProxyfierDetectionHost; + +impl ProxyfierDetectionHost for SystemProxyfierDetectionHost { + fn env_var(&self, name: &str) -> Option { + env::var(name).ok().filter(|value| !value.trim().is_empty()) + } + + fn path_exists(&self, path: &Path) -> bool { + path.exists() + } + + fn process_running(&self, process_name: &str) -> bool { + let process_name = process_name.trim_end_matches(".exe"); + let script = format!( + "if (Get-Process -Name '{}' -ErrorAction SilentlyContinue) {{ 'true' }} else {{ 'false' }}", + escape_powershell_single(process_name) + ); + + powershell_bool(&script) + } + + fn service_running(&self, service_name: &str) -> bool { + let script = format!( + "$s = Get-Service -Name '{}' -ErrorAction SilentlyContinue; if ($s -and $s.Status -eq 'Running') {{ 'true' }} else {{ 'false' }}", + escape_powershell_single(service_name) + ); + + powershell_bool(&script) + } + + fn registry_install_entries(&self) -> Vec { + read_registry_install_entries() + } +} + +pub fn detect_proxyfier_install() -> Option { + detect_proxyfier_install_with_host(&SystemProxyfierDetectionHost) +} + +pub fn detect_proxyfier_install_with_host( + host: &impl ProxyfierDetectionHost, +) -> Option { + let proxifyre_running = host.process_running("ProxiFyre.exe") + || host.service_running("ProxiFyreService") + || host.service_running("ProxiFyre"); + + proxyfier_candidates(host) + .into_iter() + .filter_map(|candidate| candidate.into_detected(host, proxifyre_running)) + .next() +} + +pub fn proxyfier_component_from_detection( + detected: Option<&DetectedProxyfier>, +) -> ComponentStatus { + match detected { + Some(proxyfier) => detected_proxyfier_component(proxyfier), + None => missing_proxyfier_component(), + } +} + +fn detected_proxyfier_component(proxyfier: &DetectedProxyfier) -> ComponentStatus { + let state = if proxyfier.running { + ComponentState::Running + } else { + ComponentState::Installed + }; + let actions = match proxyfier.engine { + ProxyfierEngine::ProxiFyre => { + if proxyfier.running { + vec![ + "Применить сгенерированный конфиг".to_string(), + "Открыть папку конфига".to_string(), + "Перезапустить".to_string(), + ] + } else { + vec![ + "Применить сгенерированный конфиг".to_string(), + "Открыть папку конфига".to_string(), + "Запустить".to_string(), + ] + } + } + }; + + ComponentStatus { + id: ComponentId::Proxyfier, + name: "ProxiFyre".to_string(), + state, + installed: true, + running: proxyfier.running, + version: Some(match proxyfier.engine { + ProxyfierEngine::ProxiFyre => "ProxiFyre найден".to_string(), + }), + path: Some(proxyfier.install_dir.display().to_string()), + problems: Vec::new(), + actions, + } +} + +fn missing_proxyfier_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Proxyfier, + name: "ProxiFyre".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["ProxiFyre нужен для маршрутизации выбранных приложений".to_string()], + actions: vec!["Установить ProxiFyre".to_string()], + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ProxyfierCandidate { + engine: ProxyfierEngine, + name: String, + install_dir: PathBuf, +} + +impl ProxyfierCandidate { + fn into_detected( + self, + host: &impl ProxyfierDetectionHost, + proxifyre_running: bool, + ) -> Option { + let executable_path = self.install_dir.join(executable_name(&self.engine)); + let config_path = config_path(&self.engine, &self.install_dir); + let exists = host.path_exists(&self.install_dir) + || host.path_exists(&executable_path) + || config_path + .as_ref() + .is_some_and(|path| host.path_exists(path)); + + if !exists { + return None; + } + + Some(DetectedProxyfier { + service_name: service_name(&self.engine).map(str::to_string), + engine: self.engine, + name: self.name, + install_dir: self.install_dir, + executable_path, + config_path, + running: proxifyre_running, + }) + } +} + +fn proxyfier_candidates(host: &impl ProxyfierDetectionHost) -> Vec { + let mut candidates = Vec::new(); + + push_env_candidate( + &mut candidates, + host, + ProxyfierEngine::ProxiFyre, + "ProxiFyre", + "VPN_PROXY_PROXIFYRE_ROOT", + ); + for entry in host.registry_install_entries() { + if let Some(engine) = engine_from_name(&entry.display_name) { + let install_dir = entry + .install_location + .or_else(|| entry.display_icon.and_then(|path| executable_parent(&path))); + if let Some(install_dir) = install_dir { + push_candidate( + &mut candidates, + ProxyfierCandidate { + name: entry.display_name, + engine, + install_dir, + }, + ); + } + } + } + + for install_dir in common_install_dirs(host, "ProxiFyre") { + push_candidate( + &mut candidates, + ProxyfierCandidate { + engine: ProxyfierEngine::ProxiFyre, + name: "ProxiFyre".to_string(), + install_dir, + }, + ); + } + candidates +} + +fn push_env_candidate( + candidates: &mut Vec, + host: &impl ProxyfierDetectionHost, + engine: ProxyfierEngine, + name: &str, + env_name: &str, +) { + if let Some(path) = host.env_var(env_name) { + push_candidate( + candidates, + ProxyfierCandidate { + engine, + name: name.to_string(), + install_dir: PathBuf::from(path), + }, + ); + } +} + +fn push_candidate(candidates: &mut Vec, candidate: ProxyfierCandidate) { + if !candidates.iter().any(|existing| { + existing.engine == candidate.engine && same_path(&existing.install_dir, &candidate.install_dir) + }) { + candidates.push(candidate); + } +} + +fn common_install_dirs(host: &impl ProxyfierDetectionHost, folder_name: &str) -> Vec { + let mut dirs = vec![PathBuf::from(format!(r"C:\Tools\{folder_name}"))]; + + for env_name in ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"] { + if let Some(root) = host.env_var(env_name) { + dirs.push(PathBuf::from(root).join(folder_name)); + } + } + + dirs +} + +fn executable_name(engine: &ProxyfierEngine) -> &'static str { + match engine { + ProxyfierEngine::ProxiFyre => "ProxiFyre.exe", + } +} + +fn config_path(engine: &ProxyfierEngine, install_dir: &Path) -> Option { + match engine { + ProxyfierEngine::ProxiFyre => Some(install_dir.join("app-config.json")), + } +} + +fn service_name(engine: &ProxyfierEngine) -> Option<&'static str> { + match engine { + ProxyfierEngine::ProxiFyre => Some("ProxiFyreService"), + } +} + +fn engine_from_name(name: &str) -> Option { + let normalized = name.to_ascii_lowercase(); + if normalized.contains("proxifyre") { + Some(ProxyfierEngine::ProxiFyre) + } else { + None + } +} + +fn executable_parent(path: &Path) -> Option { + path.parent().map(Path::to_path_buf) +} + +fn same_path(left: &Path, right: &Path) -> bool { + left.to_string_lossy() + .eq_ignore_ascii_case(&right.to_string_lossy()) +} + +fn powershell_bool(script: &str) -> bool { + Command::new("powershell") + .args(["-NoProfile", "-NonInteractive", "-Command", script]) + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .is_some_and(|stdout| stdout.trim().eq_ignore_ascii_case("true")) +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct RegistryInstallJson { + display_name: Option, + install_location: Option, + display_icon: Option, +} + +fn read_registry_install_entries() -> Vec { + let script = r#" +$paths = @( + 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' +) +$items = foreach ($path in $paths) { + Get-ItemProperty -Path $path -ErrorAction SilentlyContinue +} +$items | + Where-Object { $_.DisplayName -match 'ProxiFyre' } | + Select-Object DisplayName,InstallLocation,DisplayIcon | + ConvertTo-Json -Compress +"#; + + let Ok(output) = Command::new("powershell") + .args(["-NoProfile", "-NonInteractive", "-Command", script]) + .output() + else { + return Vec::new(); + }; + + if !output.status.success() { + return Vec::new(); + } + + let Ok(stdout) = String::from_utf8(output.stdout) else { + return Vec::new(); + }; + let stdout = stdout.trim(); + if stdout.is_empty() { + return Vec::new(); + } + + parse_registry_json(stdout) +} + +fn parse_registry_json(json: &str) -> Vec { + let Ok(value) = serde_json::from_str::(json) else { + return Vec::new(); + }; + + match value { + serde_json::Value::Array(entries) => entries + .into_iter() + .filter_map(registry_entry_from_value) + .collect(), + entry => registry_entry_from_value(entry).into_iter().collect(), + } +} + +fn registry_entry_from_value(value: serde_json::Value) -> Option { + let parsed = serde_json::from_value::(value).ok()?; + let display_name = parsed.display_name?; + Some(RegistryInstallEntry { + display_name, + install_location: parsed + .install_location + .filter(|value| !value.trim().is_empty()) + .map(PathBuf::from), + display_icon: parsed + .display_icon + .and_then(|value| display_icon_path(&value)), + }) +} + +fn display_icon_path(value: &str) -> Option { + let trimmed = value.trim().trim_matches('"'); + if trimmed.is_empty() { + return None; + } + + let without_icon_index = trimmed + .split_once(',') + .map(|(path, _)| path) + .unwrap_or(trimmed) + .trim() + .trim_matches('"'); + + Some(PathBuf::from(without_icon_index)) +} + +fn escape_powershell_single(value: &str) -> String { + value.replace('\'', "''") +} diff --git a/apps/windows-client/src-tauri/src/helper.rs b/apps/windows-client/src-tauri/src/helper.rs new file mode 100644 index 0000000..1334c29 --- /dev/null +++ b/apps/windows-client/src-tauri/src/helper.rs @@ -0,0 +1,184 @@ +use crate::models::ComponentId; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum HelperAction { + #[serde(rename = "install-control-app")] + InstallControlApp, + #[serde(rename = "install-proxyfier")] + InstallProxyfier, + #[serde(rename = "install-singbox")] + InstallSingbox, + #[serde(rename = "proxyfier.apply")] + ProxyfierApply, + #[serde(rename = "service.status")] + ServiceStatus, + #[serde(rename = "service.start")] + ServiceStart, + #[serde(rename = "service.stop")] + ServiceStop, + #[serde(rename = "service.restart")] + ServiceRestart, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelperRequest { + pub action: HelperAction, + #[serde(skip_serializing_if = "Option::is_none")] + pub component: Option, + #[serde(default)] + pub payload: Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelperResponse { + pub success: bool, + pub action: HelperAction, + pub changed: bool, + pub message: String, + #[serde(default)] + pub details: Value, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HelperCommandSpec { + pub program: PathBuf, + pub args: Vec, + pub stdin: String, + pub requires_elevation: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HelperCommandOutput { + pub status_code: i32, + pub stdout: String, + pub stderr: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelperError { + pub code: String, + pub message: String, +} + +impl HelperError { + pub fn new(code: impl Into, message: impl Into) -> Self { + Self { + code: code.into(), + message: message.into(), + } + } +} + +pub trait HelperCommandRunner { + fn run(&self, spec: &HelperCommandSpec) -> Result; +} + +#[derive(Debug, Clone)] +pub struct StructuredHelper { + helper_program: PathBuf, + runner: R, +} + +impl StructuredHelper +where + R: HelperCommandRunner, +{ + pub fn new(helper_program: impl Into, runner: R) -> Self { + Self { + helper_program: helper_program.into(), + runner, + } + } + + pub fn runner(&self) -> &R { + &self.runner + } + + pub fn execute(&self, request: &HelperRequest) -> Result { + let stdin = serde_json::to_string(request) + .map_err(|error| HelperError::new("helper_request_encode", error.to_string()))?; + let spec = HelperCommandSpec { + program: self.helper_program.clone(), + args: vec!["--json".to_string()], + stdin, + requires_elevation: helper_action_requires_elevation(&request.action), + }; + let output = self.runner.run(&spec)?; + + if output.status_code != 0 { + return Err(HelperError::new( + "helper_exit", + format!( + "Помощник завершился с кодом {}: {}", + output.status_code, output.stderr + ), + )); + } + + parse_helper_response(&output.stdout) + } +} + +pub fn parse_helper_response(stdout: &str) -> Result { + serde_json::from_str(stdout).map_err(|error| { + HelperError::new( + "helper_response_decode", + format!("Помощник вернул не JSON или некорректный JSON: {error}"), + ) + }) +} + +pub fn install_request(component: ComponentId) -> HelperRequest { + let action = match component { + ComponentId::ControlApp => HelperAction::InstallControlApp, + ComponentId::Proxyfier => HelperAction::InstallProxyfier, + ComponentId::Singbox => HelperAction::InstallSingbox, + }; + + HelperRequest { + action, + component: Some(component), + payload: json!({}), + } +} + +pub fn service_request(component: ComponentId, action: HelperAction) -> HelperRequest { + HelperRequest { + action, + component: Some(component), + payload: json!({}), + } +} + +pub fn proxifyre_apply_request( + config_path: impl AsRef, + service_name: impl Into, +) -> HelperRequest { + HelperRequest { + action: HelperAction::ProxyfierApply, + component: Some(ComponentId::Proxyfier), + payload: json!({ + "configPath": config_path.as_ref().display().to_string(), + "serviceName": service_name.into(), + }), + } +} + +pub fn helper_action_requires_elevation(action: &HelperAction) -> bool { + matches!( + action, + HelperAction::InstallControlApp + | HelperAction::InstallProxyfier + | HelperAction::InstallSingbox + | HelperAction::ProxyfierApply + | HelperAction::ServiceStart + | HelperAction::ServiceStop + | HelperAction::ServiceRestart + ) +} diff --git a/apps/windows-client/src-tauri/src/lib.rs b/apps/windows-client/src-tauri/src/lib.rs new file mode 100644 index 0000000..5504150 --- /dev/null +++ b/apps/windows-client/src-tauri/src/lib.rs @@ -0,0 +1,5 @@ +pub fn run() { + tauri::Builder::default() + .run(tauri::generate_context!()) + .expect("не удалось запустить клиент VPN Proxy для Windows"); +} diff --git a/apps/windows-client/src-tauri/src/main.rs b/apps/windows-client/src-tauri/src/main.rs new file mode 100644 index 0000000..37f0d3d --- /dev/null +++ b/apps/windows-client/src-tauri/src/main.rs @@ -0,0 +1,42 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +mod activity; +mod component_detection; +mod commands; +mod models; +mod storage; +mod validation; + +mod adapters { + pub mod proxifyre; + pub mod proxy_router; +} + +#[cfg(test)] +pub(crate) mod proxifyre { + pub use crate::adapters::proxifyre::*; +} + +#[cfg(test)] +pub(crate) mod proxy_router { + pub use crate::adapters::proxy_router::*; +} + +fn main() { + tauri::Builder::default() + .manage(commands::CommandState::default()) + .invoke_handler(tauri::generate_handler![ + commands::get_status, + commands::get_profiles, + commands::save_profile, + commands::get_targets, + commands::save_target, + commands::get_components, + commands::resolve_profile_preview, + commands::apply_profiles, + commands::get_logs, + commands::open_config_location + ]) + .run(tauri::generate_context!()) + .expect("не удалось запустить клиент VPN Proxy для Windows"); +} diff --git a/apps/windows-client/src-tauri/src/models.rs b/apps/windows-client/src-tauri/src/models.rs new file mode 100644 index 0000000..368a67c --- /dev/null +++ b/apps/windows-client/src-tauri/src/models.rs @@ -0,0 +1,167 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Protocol { + Tcp, + Udp, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ProfileItemType { + Process, + Folder, + Exe, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TargetKind { + Local, + External, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ProxyProtocol { + Socks5, + Http, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ComponentId { + ControlApp, + Proxyfier, + Singbox, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ComponentState { + Installed, + Missing, + Stopped, + Running, + Error, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProfileItemInput { + #[serde(rename = "type")] + pub item_type: String, + pub value: String, + #[serde(default)] + pub recursive: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProfileInput { + pub id: Option, + pub name: String, + #[serde(default = "default_enabled")] + pub enabled: bool, + #[serde(default = "default_target_id")] + pub target_id: String, + #[serde(default = "default_protocols")] + pub protocols: Vec, + #[serde(default)] + pub items: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProfileItem { + #[serde(rename = "type")] + pub item_type: ProfileItemType, + pub value: String, + pub recursive: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Profile { + pub id: String, + pub name: String, + pub enabled: bool, + pub target_id: String, + pub protocols: Vec, + pub items: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TargetInput { + pub id: Option, + pub name: String, + #[serde(default = "default_target_kind")] + pub kind: String, + #[serde(default = "default_proxy_protocol")] + pub protocol: String, + pub host: String, + pub port: u32, + #[serde(default)] + pub requires_component: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Target { + pub id: String, + pub name: String, + pub kind: TargetKind, + pub protocol: ProxyProtocol, + pub host: String, + pub port: u16, + pub requires_component: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ComponentStatus { + pub id: ComponentId, + pub name: String, + pub state: ComponentState, + pub installed: bool, + pub running: bool, + pub version: Option, + pub path: Option, + #[serde(default)] + pub problems: Vec, + #[serde(default)] + pub actions: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ActivityEntry { + pub id: String, + pub at: String, + pub level: ActivityLevel, + pub title: String, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ActivityLevel { + Info, + Warning, + Error, + Success, +} + +fn default_enabled() -> bool { + true +} + +fn default_target_id() -> String { + "local-singbox".to_string() +} + +fn default_protocols() -> Vec { + vec!["TCP".to_string(), "UDP".to_string()] +} + +fn default_target_kind() -> String { + "external".to_string() +} + +fn default_proxy_protocol() -> String { + "socks5".to_string() +} diff --git a/apps/windows-client/src-tauri/src/storage.rs b/apps/windows-client/src-tauri/src/storage.rs new file mode 100644 index 0000000..9b088ae --- /dev/null +++ b/apps/windows-client/src-tauri/src/storage.rs @@ -0,0 +1,187 @@ +use crate::activity::{append_activity, cap_activity, DEFAULT_ACTIVITY_LIMIT}; +use crate::models::{ActivityEntry, ComponentStatus, Profile, Target}; +use serde::{de::DeserializeOwned, Serialize}; +use std::fs; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; + +pub fn default_config_root() -> PathBuf { + PathBuf::from(r"C:\ProgramData\VpnProxy") +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StoragePaths { + pub root: PathBuf, + pub config_dir: PathBuf, + pub state_dir: PathBuf, + pub generated_dir: PathBuf, + pub profiles_file: PathBuf, + pub targets_file: PathBuf, + pub components_file: PathBuf, + pub activity_file: PathBuf, +} + +impl StoragePaths { + pub fn new(root: impl Into) -> Self { + let root = root.into(); + let config_dir = root.join("config"); + let state_dir = root.join("state"); + let generated_dir = root.join("generated"); + + Self { + root, + profiles_file: config_dir.join("profiles.json"), + targets_file: config_dir.join("targets.json"), + components_file: config_dir.join("components.json"), + activity_file: state_dir.join("activity.json"), + config_dir, + state_dir, + generated_dir, + } + } +} + +impl Default for StoragePaths { + fn default() -> Self { + Self::new(default_config_root()) + } +} + +#[derive(Debug, Clone)] +pub struct JsonStorage { + paths: StoragePaths, + activity_limit: usize, +} + +impl JsonStorage { + pub fn new(root: impl Into) -> Self { + Self::with_activity_limit(root, DEFAULT_ACTIVITY_LIMIT) + } + + pub fn with_activity_limit(root: impl Into, activity_limit: usize) -> Self { + Self { + paths: StoragePaths::new(root), + activity_limit, + } + } + + pub fn paths(&self) -> &StoragePaths { + &self.paths + } + + pub fn ensure_dirs(&self) -> io::Result<()> { + fs::create_dir_all(&self.paths.config_dir)?; + fs::create_dir_all(&self.paths.state_dir)?; + fs::create_dir_all(&self.paths.generated_dir)?; + Ok(()) + } + + pub fn read_profiles(&self) -> io::Result> { + self.read_json_or_default(&self.paths.profiles_file) + } + + pub fn write_profiles(&self, profiles: &[Profile]) -> io::Result<()> { + self.write_json(&self.paths.profiles_file, profiles) + } + + pub fn read_targets(&self) -> io::Result> { + self.read_json_or_default(&self.paths.targets_file) + } + + pub fn write_targets(&self, targets: &[Target]) -> io::Result<()> { + self.write_json(&self.paths.targets_file, targets) + } + + pub fn read_components(&self) -> io::Result> { + self.read_json_or_default(&self.paths.components_file) + } + + pub fn write_components(&self, components: &[ComponentStatus]) -> io::Result<()> { + self.write_json(&self.paths.components_file, components) + } + + pub fn read_activity(&self) -> io::Result> { + let entries = self.read_json_or_default(&self.paths.activity_file)?; + Ok(cap_activity(entries, self.activity_limit)) + } + + pub fn write_activity(&self, entries: &[ActivityEntry]) -> io::Result<()> { + let entries = cap_activity(entries.to_vec(), self.activity_limit); + self.write_json(&self.paths.activity_file, &entries) + } + + pub fn append_activity(&self, entry: ActivityEntry) -> io::Result> { + let entries = self.read_activity()?; + let entries = append_activity(entries, entry, self.activity_limit); + self.write_json(&self.paths.activity_file, &entries)?; + Ok(entries) + } + + fn read_json_or_default(&self, path: &Path) -> io::Result + where + T: DeserializeOwned + Default, + { + match fs::read_to_string(path) { + Ok(contents) => match serde_json::from_str(&contents) { + Ok(value) => Ok(value), + Err(_) => Ok(T::default()), + }, + Err(error) if error.kind() == ErrorKind::NotFound => Ok(T::default()), + Err(error) => Err(error), + } + } + + fn write_json(&self, path: &Path, value: &T) -> io::Result<()> + where + T: Serialize + ?Sized, + { + let contents = serde_json::to_vec_pretty(value) + .map_err(|error| io::Error::new(ErrorKind::InvalidData, error))?; + write_atomic(path, &contents) + } +} + +impl Default for JsonStorage { + fn default() -> Self { + Self::new(default_config_root()) + } +} + +pub fn backup_path(path: &Path) -> PathBuf { + sibling_with_suffix(path, "bak") +} + +fn temp_path(path: &Path) -> PathBuf { + sibling_with_suffix(path, "tmp") +} + +fn sibling_with_suffix(path: &Path, suffix: &str) -> PathBuf { + let file_name = path + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("storage.json"); + + path.with_file_name(format!("{file_name}.{suffix}")) +} + +fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let temp_path = temp_path(path); + fs::write(&temp_path, contents)?; + + if path.exists() { + fs::copy(path, backup_path(path))?; + fs::remove_file(path)?; + } + + match fs::rename(&temp_path, path) { + Ok(()) => Ok(()), + Err(error) => { + let _ = fs::remove_file(&temp_path); + Err(error) + } + } +} diff --git a/apps/windows-client/src-tauri/src/validation.rs b/apps/windows-client/src-tauri/src/validation.rs new file mode 100644 index 0000000..2e0cc42 --- /dev/null +++ b/apps/windows-client/src-tauri/src/validation.rs @@ -0,0 +1,222 @@ +use crate::models::{ + ComponentId, Profile, ProfileInput, ProfileItem, ProfileItemType, Protocol, ProxyProtocol, + Target, TargetInput, TargetKind, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ValidationError { + pub field: String, + pub message: String, +} + +pub type ValidationResult = Result>; + +fn error(field: impl Into, message: impl Into) -> ValidationError { + ValidationError { + field: field.into(), + message: message.into(), + } +} + +fn clean(value: &str) -> String { + value.trim().to_string() +} + +fn slug(value: &str, fallback: &str) -> String { + let mut output = String::new(); + let mut previous_dash = false; + + for ch in value.trim().to_lowercase().chars() { + if ch.is_ascii_alphanumeric() { + output.push(ch); + previous_dash = false; + } else if !previous_dash { + output.push('-'); + previous_dash = true; + } + } + + let output = output.trim_matches('-').to_string(); + if output.is_empty() { + fallback.to_string() + } else { + output + } +} + +fn process_name(value: &str) -> String { + let base = value + .trim() + .rsplit(['\\', '/']) + .next() + .unwrap_or("") + .trim(); + base.strip_suffix(".exe") + .or_else(|| base.strip_suffix(".EXE")) + .unwrap_or(base) + .trim() + .to_string() +} + +pub fn parse_protocol(value: &str) -> Result { + match value.trim().to_ascii_uppercase().as_str() { + "TCP" => Ok(Protocol::Tcp), + "UDP" => Ok(Protocol::Udp), + _ => Err(error("protocols", format!("Неподдерживаемый протокол: {value}"))), + } +} + +pub fn parse_profile_item_type(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "process" => Ok(ProfileItemType::Process), + "folder" => Ok(ProfileItemType::Folder), + "exe" => Ok(ProfileItemType::Exe), + _ => Err(error("items.type", format!("Неподдерживаемый тип элемента: {value}"))), + } +} + +pub fn parse_target_kind(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "local" => Ok(TargetKind::Local), + "external" => Ok(TargetKind::External), + _ => Err(error("kind", format!("Неподдерживаемый тип цели: {value}"))), + } +} + +pub fn parse_proxy_protocol(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "socks5" => Ok(ProxyProtocol::Socks5), + "http" => Ok(ProxyProtocol::Http), + _ => Err(error( + "protocol", + format!("Неподдерживаемый протокол прокси: {value}"), + )), + } +} + +pub fn parse_component_id(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "control-app" | "controlapp" => Ok(ComponentId::ControlApp), + "proxyfier" => Ok(ComponentId::Proxyfier), + "singbox" | "sing-box" => Ok(ComponentId::Singbox), + _ => Err(error( + "requires_component", + format!("Неподдерживаемый компонент: {value}"), + )), + } +} + +pub fn normalize_profile(input: ProfileInput) -> ValidationResult { + let mut errors = Vec::new(); + let name = clean(&input.name); + if name.is_empty() { + errors.push(error("name", "Укажите название профиля")); + } + + let target_id = clean(&input.target_id); + if target_id.is_empty() { + errors.push(error("target_id", "Укажите цель профиля")); + } + + let mut protocols = Vec::new(); + for value in input.protocols { + match parse_protocol(&value) { + Ok(protocol) if !protocols.contains(&protocol) => protocols.push(protocol), + Ok(_) => {} + Err(err) => errors.push(err), + } + } + if protocols.is_empty() { + errors.push(error("protocols", "Выберите хотя бы один протокол")); + } + + let mut items = Vec::new(); + for raw_item in input.items { + let item_type = match parse_profile_item_type(&raw_item.item_type) { + Ok(item_type) => item_type, + Err(err) => { + errors.push(err); + continue; + } + }; + + let value = match item_type { + ProfileItemType::Process => process_name(&raw_item.value), + ProfileItemType::Folder | ProfileItemType::Exe => clean(&raw_item.value), + }; + if value.is_empty() { + errors.push(error("items.value", "Укажите значение элемента профиля")); + continue; + } + + let recursive = matches!(item_type, ProfileItemType::Folder) + && raw_item.recursive.unwrap_or(true); + items.push(ProfileItem { + item_type, + value, + recursive, + }); + } + + if !errors.is_empty() { + return Err(errors); + } + + Ok(Profile { + id: slug(input.id.as_deref().unwrap_or(&name), "profile"), + name, + enabled: input.enabled, + target_id, + protocols, + items, + }) +} + +pub fn normalize_target(input: TargetInput) -> ValidationResult { + let mut errors = Vec::new(); + let name = clean(&input.name); + let host = clean(&input.host); + + if name.is_empty() { + errors.push(error("name", "Укажите название цели")); + } + if host.is_empty() { + errors.push(error("host", "Укажите хост цели")); + } + if input.port == 0 || input.port > u16::MAX as u32 { + errors.push(error("port", "Порт цели должен быть от 1 до 65535")); + } + + let kind = parse_target_kind(&input.kind).unwrap_or_else(|err| { + errors.push(err); + TargetKind::External + }); + let protocol = parse_proxy_protocol(&input.protocol).unwrap_or_else(|err| { + errors.push(err); + ProxyProtocol::Socks5 + }); + let requires_component = match input.requires_component { + Some(value) if !value.trim().is_empty() => match parse_component_id(&value) { + Ok(component) => Some(component), + Err(err) => { + errors.push(err); + None + } + }, + _ => None, + }; + + if !errors.is_empty() { + return Err(errors); + } + + Ok(Target { + id: slug(input.id.as_deref().unwrap_or(&name), "target"), + name, + kind, + protocol, + host, + port: input.port as u16, + requires_component, + }) +} diff --git a/apps/windows-client/src-tauri/tauri.conf.json b/apps/windows-client/src-tauri/tauri.conf.json new file mode 100644 index 0000000..6dc1e79 --- /dev/null +++ b/apps/windows-client/src-tauri/tauri.conf.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "VPN Proxy для Windows", + "version": "0.1.0", + "identifier": "ru.dokops.vpn-proxy.windows", + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devUrl": "http://localhost:5173", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "VPN Proxy для Windows", + "width": 1120, + "height": 760, + "minWidth": 760, + "minHeight": 560, + "resizable": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.ico" + ] + } +} diff --git a/apps/windows-client/src-tauri/tests/command_tests.rs b/apps/windows-client/src-tauri/tests/command_tests.rs new file mode 100644 index 0000000..d01f948 --- /dev/null +++ b/apps/windows-client/src-tauri/tests/command_tests.rs @@ -0,0 +1,442 @@ +#[path = "../src/activity.rs"] +mod activity; +#[path = "../src/component_detection.rs"] +mod component_detection; +#[path = "../src/commands.rs"] +mod commands; +#[path = "../src/models.rs"] +mod models; +#[path = "../src/adapters/proxifyre.rs"] +mod proxifyre; +#[path = "../src/adapters/proxy_router.rs"] +mod proxy_router; +#[path = "../src/storage.rs"] +mod storage; +#[path = "../src/validation.rs"] +mod validation; + +use commands::{ + apply_profiles_with_services, build_status, resolve_component_statuses, resolve_preview, + save_profile_to_storage, save_target_to_storage, Clock, CommandError, + DetectedProxyApplyHelper, HelperApplyRequest, HelperApplyResult, ProfileInputDto, + ProfileItemInputDto, ProxyApplyHelper, TargetInputDto, +}; +use component_detection::{ + DetectedProxyfier, ProxyfierDetectionHost, ProxyfierEngine, RegistryInstallEntry, +}; +use models::{ + ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol, + ProxyProtocol, Target, TargetKind, +}; +use proxifyre::ProxiFyreAdapter; +use std::collections::HashSet; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; +use storage::JsonStorage; + +#[test] +fn save_commands_normalize_and_persist_profile_and_target() { + let root = test_root("save"); + let storage = JsonStorage::new(root.clone()); + + let target = save_target_to_storage( + &storage, + TargetInputDto { + id: Some("Home Gateway".to_string()), + name: " Home Gateway ".to_string(), + kind: Some("external".to_string()), + protocol: Some("socks5".to_string()), + host: " 192.168.50.111 ".to_string(), + port: 8080, + requires_component: None, + }, + ) + .expect("target command should normalize"); + let profile = save_profile_to_storage( + &storage, + ProfileInputDto { + id: Some("Discord".to_string()), + name: " Discord ".to_string(), + enabled: Some(true), + target_id: Some("home-gateway".to_string()), + protocols: Some(vec!["tcp".to_string(), "UDP".to_string()]), + items: Some(vec![ProfileItemInputDto { + item_type: "process".to_string(), + value: "Discord.exe".to_string(), + recursive: None, + }]), + }, + ) + .expect("profile command should normalize"); + + assert_eq!(target.id, "home-gateway"); + assert_eq!(profile.id, "discord"); + assert_eq!(profile.target_id, "home-gateway"); + assert_eq!(profile.items[0].value, "Discord"); + + let status = build_status(&storage).expect("status command should read stored state"); + assert_eq!( + status.route_line, + "Выбранные приложения -> ProxiFyre -> внешний прокси 192.168.50.111:8080" + ); + assert_eq!(status.active_profile_count, 1); + assert_eq!(status.routed_app_count, 1); + + cleanup(&root); +} + +#[test] +fn resolve_preview_returns_structured_apps_without_filesystem_scan() { + let preview = resolve_preview(ProfileInputDto { + id: Some("Game".to_string()), + name: "Game".to_string(), + enabled: Some(true), + target_id: Some("home-gateway".to_string()), + protocols: Some(vec!["TCP".to_string()]), + items: Some(vec![ + ProfileItemInputDto { + item_type: "process".to_string(), + value: "Discord.exe".to_string(), + recursive: None, + }, + ProfileItemInputDto { + item_type: "folder".to_string(), + value: r"C:\Games\Launcher".to_string(), + recursive: Some(true), + }, + ]), + }) + .expect("preview should normalize profile input"); + + assert_eq!(preview.profile_id, "game"); + assert_eq!(preview.apps.len(), 2); + assert_eq!(preview.apps[0].app_name, "Discord"); + assert_eq!(preview.apps[1].source_type, ProfileItemType::Folder); + assert!(preview + .warnings + .iter() + .any(|warning| warning.contains("Сканирование папок отложено"))); +} + +#[test] +fn apply_generates_derived_config_and_records_activity_with_mock_helper() { + let root = test_root("apply"); + let storage = JsonStorage::new(root.clone()); + storage + .write_profiles(&[discord_profile("home-gateway")]) + .expect("write profiles"); + storage + .write_targets(&[external_socks5_target()]) + .expect("write targets"); + storage + .write_components(&[proxyfier_running(), singbox_missing()]) + .expect("write components"); + + let response = apply_profiles_with_services( + &storage, + &ProxiFyreAdapter::default(), + &MockApplyHelper, + &FixedClock, + ) + .expect("apply command should generate config and call helper"); + + let generated_path = PathBuf::from(&response.generated_config_path); + let generated_contents = fs::read_to_string(&generated_path).expect("read generated config"); + let activity = storage.read_activity().expect("read activity"); + + assert!(response.success); + assert!(response.changed); + assert_eq!(response.adapter_id, "proxifyre"); + assert_eq!(response.enabled_profiles, 1); + assert_eq!(response.routed_apps, 1); + assert_eq!(response.helper.action, "proxyfier.apply.mock"); + assert!(generated_contents.contains("\"appNames\"")); + assert!(generated_contents.contains("Discord")); + assert!(generated_path.ends_with("proxifyre-app-config.json")); + assert_eq!(activity.len(), 1); + assert_eq!(activity[0].at, "2026-07-03T00:00:00Z"); + assert_eq!(activity[0].title, "Конфиг ProxiFyre создан"); + + cleanup(&root); +} + +#[test] +fn apply_blocks_local_singbox_target_when_component_is_missing() { + let root = test_root("missing-singbox"); + let storage = JsonStorage::new(root.clone()); + storage + .write_profiles(&[discord_profile("local-singbox")]) + .expect("write profiles"); + storage + .write_targets(&[local_singbox_target()]) + .expect("write targets"); + storage + .write_components(&[singbox_missing()]) + .expect("write components"); + + let error = apply_profiles_with_services( + &storage, + &ProxiFyreAdapter::default(), + &MockApplyHelper, + &FixedClock, + ) + .expect_err("missing sing-box should block local target apply"); + let activity = storage.read_activity().expect("read blocked activity"); + + assert_eq!(error.code, "required_component_not_running"); + assert_eq!(activity.len(), 1); + assert_eq!(activity[0].level, models::ActivityLevel::Error); + assert_eq!(activity[0].title, "Применение ProxiFyre заблокировано"); + + cleanup(&root); +} + +#[test] +fn component_status_merges_detected_existing_proxifyre() { + let components = resolve_component_statuses( + Vec::new(), + Some(DetectedProxyfier { + engine: ProxyfierEngine::ProxiFyre, + name: "ProxiFyre".to_string(), + install_dir: PathBuf::from(r"C:\Tools\ProxiFyre"), + executable_path: PathBuf::from(r"C:\Tools\ProxiFyre\ProxiFyre.exe"), + config_path: Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json")), + running: true, + service_name: Some("ProxiFyreService".to_string()), + }), + ); + let proxyfier = components + .iter() + .find(|component| component.id == ComponentId::Proxyfier) + .expect("proxyfier component"); + + assert_eq!(proxyfier.state, ComponentState::Running); + assert!(proxyfier.installed); + assert!(proxyfier.running); + assert_eq!(proxyfier.path, Some(r"C:\Tools\ProxiFyre".to_string())); + assert!(proxyfier.problems.is_empty()); +} + +#[test] +fn detected_proxy_apply_helper_writes_proxifyre_app_config() { + let root = test_root("detected-proxifyre"); + let install_dir = root.join("ProxiFyre"); + fs::create_dir_all(&install_dir).expect("install dir"); + fs::write(install_dir.join("ProxiFyre.exe"), "mock exe").expect("mock exe"); + fs::write(install_dir.join("app-config.json"), "{}").expect("existing config"); + let generated_config = root.join("generated").join("proxifyre-app-config.json"); + let host = DetectionHost::new() + .with_registry("ProxiFyre", &install_dir) + .with_path(&install_dir) + .with_path(&install_dir.join("ProxiFyre.exe")); + let helper = DetectedProxyApplyHelper::new(host); + + let result = helper + .apply_proxy_config(HelperApplyRequest { + adapter_id: "proxifyre", + config_path: &generated_config, + config_contents: r#"{"proxies":[]}"#, + }) + .expect("detected helper should apply"); + + let applied = fs::read_to_string(install_dir.join("app-config.json")) + .expect("read applied app-config"); + + assert!(result.success); + assert!(result.changed); + assert_eq!(result.action, "proxifyre.apply-detected-config"); + assert_eq!(applied, r#"{"proxies":[]}"#); + assert!(install_dir.join("app-config.json.bak").exists()); + + cleanup(&root); +} + +#[test] +fn detected_proxy_apply_helper_ignores_plain_proxifier_install() { + let root = test_root("detected-proxifier"); + let install_dir = root.join("Proxifier"); + fs::create_dir_all(&install_dir).expect("install dir"); + fs::write(install_dir.join("Proxifier.exe"), "mock exe").expect("mock exe"); + let generated_config = root.join("generated").join("proxifyre-app-config.json"); + let host = DetectionHost::new() + .with_registry("Proxifier", &install_dir) + .with_path(&install_dir) + .with_path(&install_dir.join("Proxifier.exe")); + let helper = DetectedProxyApplyHelper::new(host); + + let result = helper + .apply_proxy_config(HelperApplyRequest { + adapter_id: "proxifyre", + config_path: &generated_config, + config_contents: r#"{"proxies":[]}"#, + }) + .expect("plain Proxifier should be ignored and config should be staged"); + + assert!(result.success); + assert!(result.changed); + assert_eq!(result.action, "proxifyre.stage-generated-config"); + assert!(result.message.contains("совместимая установка ProxiFyre не найдена")); + + cleanup(&root); +} + +struct MockApplyHelper; + +impl ProxyApplyHelper for MockApplyHelper { + fn apply_proxy_config( + &self, + request: HelperApplyRequest<'_>, + ) -> Result { + assert_eq!(request.adapter_id, "proxifyre"); + assert!(request.config_contents.contains("Discord")); + assert!(request.config_path.ends_with("proxifyre-app-config.json")); + + Ok(HelperApplyResult { + success: true, + changed: true, + action: "proxyfier.apply.mock".to_string(), + message: "Mock helper accepted generated ProxiFyre config".to_string(), + }) + } +} + +struct FixedClock; + +impl Clock for FixedClock { + fn now(&self) -> String { + "2026-07-03T00:00:00Z".to_string() + } +} + +#[derive(Default)] +struct DetectionHost { + paths: HashSet, + registry: Vec, +} + +impl DetectionHost { + fn new() -> Self { + Self::default() + } + + fn with_path(mut self, path: &Path) -> Self { + self.paths.insert(normalize_path(path)); + self + } + + fn with_registry(mut self, display_name: &str, install_location: &Path) -> Self { + self.registry.push(RegistryInstallEntry { + display_name: display_name.to_string(), + install_location: Some(install_location.to_path_buf()), + display_icon: None, + }); + self + } +} + +impl ProxyfierDetectionHost for DetectionHost { + fn env_var(&self, _name: &str) -> Option { + None + } + + fn path_exists(&self, path: &Path) -> bool { + self.paths.contains(&normalize_path(path)) + } + + fn process_running(&self, _process_name: &str) -> bool { + false + } + + fn service_running(&self, _service_name: &str) -> bool { + false + } + + fn registry_install_entries(&self) -> Vec { + self.registry.clone() + } +} + +fn normalize_path(path: &Path) -> String { + path.display().to_string().replace('/', "\\").to_ascii_lowercase() +} + +fn test_root(name: &str) -> PathBuf { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock before unix epoch") + .as_nanos(); + + std::env::temp_dir().join(format!("vpn-proxy-commands-{name}-{timestamp}")) +} + +fn cleanup(root: &Path) { + let _ = fs::remove_dir_all(root); +} + +fn discord_profile(target_id: &str) -> Profile { + Profile { + id: "discord".to_string(), + name: "Discord".to_string(), + enabled: true, + target_id: target_id.to_string(), + protocols: vec![Protocol::Tcp, Protocol::Udp], + items: vec![ProfileItem { + item_type: ProfileItemType::Process, + value: "Discord".to_string(), + recursive: false, + }], + } +} + +fn external_socks5_target() -> Target { + Target { + id: "home-gateway".to_string(), + name: "Домашний шлюз".to_string(), + kind: TargetKind::External, + protocol: ProxyProtocol::Socks5, + host: "192.168.50.111".to_string(), + port: 8080, + requires_component: None, + } +} + +fn local_singbox_target() -> Target { + Target { + id: "local-singbox".to_string(), + name: "Локальный sing-box".to_string(), + kind: TargetKind::Local, + protocol: ProxyProtocol::Socks5, + host: "127.0.0.1".to_string(), + port: 1080, + requires_component: Some(ComponentId::Singbox), + } +} + +fn proxyfier_running() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Proxyfier, + name: "ProxiFyre".to_string(), + state: ComponentState::Running, + installed: true, + running: true, + version: Some("2.2.1".to_string()), + path: Some(r"C:\Tools\ProxiFyre".to_string()), + problems: Vec::new(), + actions: vec!["Restart".to_string()], + } +} + +fn singbox_missing() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Локальный sing-box".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["Локальный sing-box не установлен".to_string()], + actions: vec!["Установить локальный sing-box".to_string()], + } +} diff --git a/apps/windows-client/src-tauri/tests/component_detection_tests.rs b/apps/windows-client/src-tauri/tests/component_detection_tests.rs new file mode 100644 index 0000000..7dce85b --- /dev/null +++ b/apps/windows-client/src-tauri/tests/component_detection_tests.rs @@ -0,0 +1,141 @@ +#[path = "../src/component_detection.rs"] +mod component_detection; +#[path = "../src/models.rs"] +mod models; + +use component_detection::{ + detect_proxyfier_install_with_host, proxyfier_component_from_detection, ProxyfierDetectionHost, + ProxyfierEngine, RegistryInstallEntry, +}; +use models::{ComponentState}; +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, +}; + +#[test] +fn detects_existing_proxifyre_from_registry_install_location() { + let host = MockHost::new() + .with_registry("ProxiFyre", r"C:\Tools\ProxiFyre") + .with_path(r"C:\Tools\ProxiFyre") + .with_service("ProxiFyreService"); + + let detected = detect_proxyfier_install_with_host(&host) + .expect("existing ProxiFyre install should be detected"); + + assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre); + assert_eq!(detected.install_dir, PathBuf::from(r"C:\Tools\ProxiFyre")); + assert_eq!(detected.config_path, Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json"))); + assert!(detected.running); + + let component = proxyfier_component_from_detection(Some(&detected)); + assert_eq!(component.state, ComponentState::Running); + assert!(component.installed); + assert!(component.running); + assert_eq!(component.path, Some(r"C:\Tools\ProxiFyre".to_string())); + assert!(component.problems.is_empty()); +} + +#[test] +fn ignores_plain_proxifier_install() { + let host = MockHost::new() + .with_registry("Proxifier", r"C:\Program Files\Proxifier") + .with_path(r"C:\Program Files\Proxifier") + .with_process("Proxifier.exe"); + + assert!(detect_proxyfier_install_with_host(&host).is_none()); +} + +#[test] +fn env_override_can_point_to_portable_proxifyre_install() { + let host = MockHost::new() + .with_env("VPN_PROXY_PROXIFYRE_ROOT", r"D:\Portable\ProxiFyre") + .with_path(r"D:\Portable\ProxiFyre\ProxiFyre.exe"); + + let detected = detect_proxyfier_install_with_host(&host) + .expect("env override should be checked before common paths"); + + assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre); + assert_eq!(detected.executable_path, PathBuf::from(r"D:\Portable\ProxiFyre\ProxiFyre.exe")); +} + +#[test] +fn missing_proxyfier_returns_install_action_status() { + let component = proxyfier_component_from_detection(None); + + assert_eq!(component.state, ComponentState::Missing); + assert!(!component.installed); + assert_eq!(component.actions, vec!["Установить ProxiFyre"]); +} + +#[derive(Default)] +struct MockHost { + env: HashMap, + paths: HashSet, + processes: HashSet, + services: HashSet, + registry: Vec, +} + +impl MockHost { + fn new() -> Self { + Self::default() + } + + fn with_env(mut self, name: &str, value: &str) -> Self { + self.env.insert(name.to_string(), value.to_string()); + self + } + + fn with_path(mut self, path: &str) -> Self { + self.paths.insert(normalize_path(path)); + self + } + + fn with_process(mut self, process: &str) -> Self { + self.processes.insert(process.to_ascii_lowercase()); + self + } + + fn with_service(mut self, service: &str) -> Self { + self.services.insert(service.to_ascii_lowercase()); + self + } + + fn with_registry(mut self, display_name: &str, install_location: &str) -> Self { + self.registry.push(RegistryInstallEntry { + display_name: display_name.to_string(), + install_location: Some(PathBuf::from(install_location)), + display_icon: None, + }); + self + } +} + +impl ProxyfierDetectionHost for MockHost { + fn env_var(&self, name: &str) -> Option { + self.env.get(name).cloned() + } + + fn path_exists(&self, path: &Path) -> bool { + self.paths.contains(&normalize_path(&path.display().to_string())) + } + + fn process_running(&self, process_name: &str) -> bool { + self.processes + .contains(&process_name.to_ascii_lowercase()) + } + + fn service_running(&self, service_name: &str) -> bool { + self.services + .contains(&service_name.to_ascii_lowercase()) + } + + fn registry_install_entries(&self) -> Vec { + self.registry.clone() + } +} + +fn normalize_path(path: &str) -> String { + path.replace('/', "\\").to_ascii_lowercase() +} diff --git a/apps/windows-client/src-tauri/tests/domain_tests.rs b/apps/windows-client/src-tauri/tests/domain_tests.rs new file mode 100644 index 0000000..76459c8 --- /dev/null +++ b/apps/windows-client/src-tauri/tests/domain_tests.rs @@ -0,0 +1,130 @@ +#[path = "../src/models.rs"] +mod models; +#[path = "../src/validation.rs"] +mod validation; + +use models::{ + ComponentId, ProfileInput, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol, + TargetInput, TargetKind, +}; +use validation::{normalize_profile, normalize_target}; + +#[test] +fn normalizes_profile_source_items() { + let profile = normalize_profile(ProfileInput { + id: Some("Discord + Vesktop".to_string()), + name: " Discord + Vesktop ".to_string(), + enabled: true, + target_id: " home-gateway ".to_string(), + protocols: vec!["tcp".to_string(), "UDP".to_string(), "TCP".to_string()], + items: vec![ + ProfileItemInput { + item_type: "process".to_string(), + value: "Discord.exe".to_string(), + recursive: None, + }, + ProfileItemInput { + item_type: "folder".to_string(), + value: "%LOCALAPPDATA%\\Vesktop".to_string(), + recursive: Some(true), + }, + ProfileItemInput { + item_type: "exe".to_string(), + value: "C:\\Games\\Game\\game.exe".to_string(), + recursive: Some(true), + }, + ], + }) + .expect("profile should normalize"); + + assert_eq!(profile.id, "discord-vesktop"); + assert_eq!(profile.name, "Discord + Vesktop"); + assert_eq!(profile.target_id, "home-gateway"); + assert_eq!(profile.protocols, vec![Protocol::Tcp, Protocol::Udp]); + assert_eq!(profile.items[0].item_type, ProfileItemType::Process); + assert_eq!(profile.items[0].value, "Discord"); + assert!(!profile.items[0].recursive); + assert_eq!(profile.items[1].item_type, ProfileItemType::Folder); + assert!(profile.items[1].recursive); + assert_eq!(profile.items[2].item_type, ProfileItemType::Exe); + assert!(!profile.items[2].recursive); +} + +#[test] +fn rejects_unsupported_profile_protocols() { + let error = normalize_profile(ProfileInput { + id: None, + name: "Bad protocol".to_string(), + enabled: true, + target_id: "home-gateway".to_string(), + protocols: vec!["icmp".to_string()], + items: vec![ProfileItemInput { + item_type: "process".to_string(), + value: "Discord".to_string(), + recursive: None, + }], + }) + .expect_err("unsupported protocol should fail"); + + assert!(error.iter().any(|item| item.field == "protocols")); +} + +#[test] +fn normalizes_external_target_without_local_singbox() { + let target = normalize_target(TargetInput { + id: Some("Home Gateway".to_string()), + name: " Home Gateway ".to_string(), + kind: "external".to_string(), + protocol: "socks5".to_string(), + host: " 192.168.50.111 ".to_string(), + port: 8080, + requires_component: None, + }) + .expect("external target should normalize"); + + assert_eq!(target.id, "home-gateway"); + assert_eq!(target.kind, TargetKind::External); + assert_eq!(target.protocol, ProxyProtocol::Socks5); + assert_eq!(target.host, "192.168.50.111"); + assert_eq!(target.port, 8080); + assert_eq!(target.requires_component, None); +} + +#[test] +fn local_singbox_target_can_exist_before_component_is_installed() { + let target = normalize_target(TargetInput { + id: Some("local-singbox".to_string()), + name: "Local sing-box".to_string(), + kind: "local".to_string(), + protocol: "socks5".to_string(), + host: "127.0.0.1".to_string(), + port: 1080, + requires_component: Some("singbox".to_string()), + }) + .expect("local target definition should not require installed component"); + + assert_eq!(target.kind, TargetKind::Local); + assert_eq!(target.requires_component, Some(ComponentId::Singbox)); +} + +#[test] +fn rejects_malformed_target_fields() { + let error = normalize_target(TargetInput { + id: None, + name: "".to_string(), + kind: "external".to_string(), + protocol: "ftp".to_string(), + host: "".to_string(), + port: 70_000, + requires_component: Some("unknown".to_string()), + }) + .expect_err("invalid target should fail"); + + assert!(error.iter().any(|item| item.field == "name")); + assert!(error.iter().any(|item| item.field == "host")); + assert!(error.iter().any(|item| item.field == "port")); + assert!(error.iter().any(|item| item.field == "protocol")); + assert!(error + .iter() + .any(|item| item.field == "requires_component")); +} diff --git a/apps/windows-client/src-tauri/tests/helper_tests.rs b/apps/windows-client/src-tauri/tests/helper_tests.rs new file mode 100644 index 0000000..9ec7aea --- /dev/null +++ b/apps/windows-client/src-tauri/tests/helper_tests.rs @@ -0,0 +1,139 @@ +#[path = "../src/helper.rs"] +mod helper; +#[path = "../src/models.rs"] +mod models; + +use helper::{ + helper_action_requires_elevation, install_request, parse_helper_response, + proxifyre_apply_request, service_request, HelperAction, HelperCommandOutput, + HelperCommandRunner, HelperCommandSpec, HelperError, HelperResponse, StructuredHelper, +}; +use models::ComponentId; +use serde_json::json; +use std::cell::RefCell; +use std::path::PathBuf; + +#[test] +fn structured_helper_serializes_request_and_parses_json_response() { + let runner = MockRunner { + output: HelperCommandOutput { + status_code: 0, + stdout: serde_json::to_string(&HelperResponse { + success: true, + action: HelperAction::ProxyfierApply, + changed: true, + message: "Applied".to_string(), + details: json!({ "serviceName": "ProxiFyreService" }), + }) + .expect("response json"), + stderr: String::new(), + }, + seen: RefCell::new(Vec::new()), + }; + let helper = StructuredHelper::new("vpn-proxy-helper.exe", runner); + + let response = helper + .execute(&proxifyre_apply_request( + r"C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json", + "ProxiFyreService", + )) + .expect("helper response"); + + assert!(response.success); + assert_eq!(response.action, HelperAction::ProxyfierApply); + assert_eq!(response.details["serviceName"], "ProxiFyreService"); +} + +#[test] +fn helper_runner_receives_json_stdin_and_elevation_flag() { + let runner = MockRunner { + output: HelperCommandOutput { + status_code: 0, + stdout: r#"{"success":true,"action":"service.restart","changed":true,"message":"Restarted","details":{}}"#.to_string(), + stderr: String::new(), + }, + seen: RefCell::new(Vec::new()), + }; + let helper = StructuredHelper::new("vpn-proxy-helper.exe", runner); + let request = service_request(ComponentId::Proxyfier, HelperAction::ServiceRestart); + + let _ = helper.execute(&request).expect("helper response"); + let seen = helper.runner().seen.borrow(); + let spec = seen.first().expect("runner should be called"); + let stdin: serde_json::Value = serde_json::from_str(&spec.stdin).expect("stdin json"); + + assert_eq!(spec.program, PathBuf::from("vpn-proxy-helper.exe")); + assert_eq!(spec.args, vec!["--json"]); + assert!(spec.requires_elevation); + assert_eq!(stdin["action"], "service.restart"); + assert_eq!(stdin["component"], "proxyfier"); +} + +#[test] +fn install_requests_are_explicit_component_actions() { + let control = install_request(ComponentId::ControlApp); + let proxyfier = install_request(ComponentId::Proxyfier); + let singbox = install_request(ComponentId::Singbox); + + assert_eq!(control.action, HelperAction::InstallControlApp); + assert_eq!(proxyfier.action, HelperAction::InstallProxyfier); + assert_eq!(singbox.action, HelperAction::InstallSingbox); + assert!(helper_action_requires_elevation(&proxyfier.action)); +} + +#[test] +fn apply_request_does_not_encode_installer_action() { + let request = proxifyre_apply_request( + r"C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json", + "ProxiFyreService", + ); + + assert_eq!(request.action, HelperAction::ProxyfierApply); + assert_eq!(request.component, Some(ComponentId::Proxyfier)); + assert_eq!( + request.payload["configPath"], + r"C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json" + ); +} + +#[test] +fn non_json_helper_stdout_is_rejected() { + let error = parse_helper_response("Proxyfier restarted successfully") + .expect_err("raw stdout should not be accepted"); + + assert_eq!(error.code, "helper_response_decode"); +} + +#[test] +fn failed_helper_exit_is_structured_error() { + let runner = MockRunner { + output: HelperCommandOutput { + status_code: 5, + stdout: String::new(), + stderr: "Access denied".to_string(), + }, + seen: RefCell::new(Vec::new()), + }; + let helper = StructuredHelper::new("vpn-proxy-helper.exe", runner); + let error = helper + .execute(&service_request( + ComponentId::Proxyfier, + HelperAction::ServiceRestart, + )) + .expect_err("failed exit should become helper error"); + + assert_eq!(error.code, "helper_exit"); + assert!(error.message.contains("Access denied")); +} + +struct MockRunner { + output: HelperCommandOutput, + seen: RefCell>, +} + +impl HelperCommandRunner for MockRunner { + fn run(&self, spec: &HelperCommandSpec) -> Result { + self.seen.borrow_mut().push(spec.clone()); + Ok(self.output.clone()) + } +} diff --git a/apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs b/apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs new file mode 100644 index 0000000..2d062f1 --- /dev/null +++ b/apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs @@ -0,0 +1,178 @@ +#[path = "../src/models.rs"] +mod models; +#[path = "../src/adapters/proxy_router.rs"] +mod proxy_router; +#[path = "../src/adapters/proxifyre.rs"] +mod proxifyre; + +use models::{ + ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol, + ProxyProtocol, Target, TargetKind, +}; +use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, PROXIFYRE_OUTPUT_FILE}; +use proxy_router::{ProxyRouterAdapter, ProxyRouterErrorKind, ProxyRouterRequest}; + +#[test] +fn generates_proxifyre_config_for_discord_external_socks5_target() { + let adapter = ProxiFyreAdapter::default(); + let profiles = vec![discord_profile("home-gateway")]; + let targets = vec![external_socks5_target()]; + let components = vec![missing_singbox_component()]; + + let generated = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect("external socks5 target should not require sing-box"); + let config: ProxiFyreConfig = + serde_json::from_str(&generated.contents).expect("generated config json"); + + assert_eq!(generated.adapter_id, "proxifyre"); + assert_eq!(generated.output_file_name, PROXIFYRE_OUTPUT_FILE); + assert_eq!(generated.enabled_profiles, 1); + assert_eq!(generated.routed_apps, 1); + assert_eq!(config.log_level, "Info"); + assert!(config.bypass_lan); + assert_eq!(config.proxies.len(), 1); + assert_eq!(config.proxies[0].app_names, vec!["Discord"]); + assert_eq!(config.proxies[0].socks5_proxy_endpoint, "192.168.50.111:8080"); + assert_eq!(config.proxies[0].supported_protocols, vec!["TCP", "UDP"]); +} + +#[test] +fn skips_disabled_profiles_when_generating_proxifyre_config() { + let adapter = ProxiFyreAdapter::default(); + let mut disabled = discord_profile("home-gateway"); + disabled.enabled = false; + let profiles = vec![disabled]; + let targets = vec![external_socks5_target()]; + let components = Vec::new(); + + let generated = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect("disabled profiles should produce empty config"); + let config: ProxiFyreConfig = + serde_json::from_str(&generated.contents).expect("generated config json"); + + assert_eq!(generated.enabled_profiles, 0); + assert_eq!(generated.routed_apps, 0); + assert!(config.proxies.is_empty()); +} + +#[test] +fn blocks_local_singbox_target_when_required_component_is_missing() { + let adapter = ProxiFyreAdapter::default(); + let profiles = vec![discord_profile("local-singbox")]; + let targets = vec![local_singbox_target()]; + let components = Vec::new(); + + let error = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect_err("local sing-box target should require installed running sing-box"); + + assert_eq!(error.kind, ProxyRouterErrorKind::MissingRequiredComponent); +} + +#[test] +fn local_singbox_target_generates_when_required_component_is_running() { + let adapter = ProxiFyreAdapter::default(); + let profiles = vec![discord_profile("local-singbox")]; + let targets = vec![local_singbox_target()]; + let components = vec![running_singbox_component()]; + + let generated = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect("running sing-box should satisfy local target dependency"); + let config: ProxiFyreConfig = + serde_json::from_str(&generated.contents).expect("generated config json"); + + assert_eq!(config.proxies.len(), 1); + assert_eq!(config.proxies[0].socks5_proxy_endpoint, "127.0.0.1:1080"); +} + +#[test] +fn rejects_http_target_because_proxifyre_adapter_is_socks5_only() { + let adapter = ProxiFyreAdapter::default(); + let profiles = vec![discord_profile("office-http")]; + let targets = vec![Target { + id: "office-http".to_string(), + name: "Office HTTP".to_string(), + kind: TargetKind::External, + protocol: ProxyProtocol::Http, + host: "192.168.50.111".to_string(), + port: 3128, + requires_component: None, + }]; + let components = Vec::new(); + + let error = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect_err("ProxiFyre should reject HTTP targets"); + + assert_eq!(error.kind, ProxyRouterErrorKind::UnsupportedTargetProtocol); +} + +fn discord_profile(target_id: &str) -> Profile { + Profile { + id: "discord".to_string(), + name: "Discord".to_string(), + enabled: true, + target_id: target_id.to_string(), + protocols: vec![Protocol::Tcp, Protocol::Udp], + items: vec![ProfileItem { + item_type: ProfileItemType::Process, + value: "Discord".to_string(), + recursive: false, + }], + } +} + +fn external_socks5_target() -> Target { + Target { + id: "home-gateway".to_string(), + name: "Home Gateway".to_string(), + kind: TargetKind::External, + protocol: ProxyProtocol::Socks5, + host: "192.168.50.111".to_string(), + port: 8080, + requires_component: None, + } +} + +fn local_singbox_target() -> Target { + Target { + id: "local-singbox".to_string(), + name: "Local sing-box".to_string(), + kind: TargetKind::Local, + protocol: ProxyProtocol::Socks5, + host: "127.0.0.1".to_string(), + port: 1080, + requires_component: Some(ComponentId::Singbox), + } +} + +fn missing_singbox_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Local sing-box".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["Local sing-box is not installed".to_string()], + actions: vec!["Install Local sing-box".to_string()], + } +} + +fn running_singbox_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Local sing-box".to_string(), + state: ComponentState::Running, + installed: true, + running: true, + version: Some("1.11.0".to_string()), + path: Some(r"C:\Tools\VpnProxy\sing-box\sing-box.exe".to_string()), + problems: Vec::new(), + actions: vec!["Restart".to_string(), "Stop".to_string()], + } +} diff --git a/apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs b/apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs new file mode 100644 index 0000000..b1e6d4d --- /dev/null +++ b/apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs @@ -0,0 +1,282 @@ +#[path = "../src/models.rs"] +mod models; +#[path = "../src/adapters/proxy_router.rs"] +mod proxy_router; +#[path = "../src/adapters/proxifyre.rs"] +mod proxifyre; +#[path = "../src/adapters/singbox.rs"] +mod singbox; + +use models::{ + ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol, + ProxyProtocol, Target, TargetKind, +}; +use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig}; +use proxy_router::{ProxyRouterAdapter, ProxyRouterRequest}; +use singbox::{ + SingBoxAdapter, SingBoxCheckResult, SingBoxConfig, SingBoxConfigChecker, + SingBoxConfigError, SingBoxConfigErrorKind, SingBoxGenerationRequest, SINGBOX_OUTPUT_FILE, +}; +use std::{ + cell::RefCell, + path::{Path, PathBuf}, +}; + +#[test] +fn generates_local_singbox_config_and_runs_check_when_binary_path_is_supplied() { + let adapter = SingBoxAdapter::default(); + let targets = vec![local_singbox_target()]; + let components = vec![running_singbox_component()]; + let checker = RecordingChecker::ok("configuration OK"); + let binary_path = Path::new(r"C:\Tools\VpnProxy\sing-box\sing-box.exe"); + + let generated = adapter + .generate_config( + SingBoxGenerationRequest::new(&targets, &components, Some(binary_path)), + &checker, + ) + .expect("running local sing-box should generate config"); + let config: SingBoxConfig = + serde_json::from_str(&generated.contents).expect("generated sing-box json"); + + assert_eq!(generated.adapter_id, "singbox"); + assert_eq!(generated.output_file_name, SINGBOX_OUTPUT_FILE); + assert_eq!(generated.local_target_id, "local-singbox"); + assert_eq!(generated.listen, "127.0.0.1"); + assert_eq!(generated.listen_port, 1080); + assert_eq!( + generated.check, + Some(SingBoxCheckResult { + checked: true, + success: true, + message: "configuration OK".to_string(), + }) + ); + assert_eq!(config.log.level, "info"); + assert_eq!(config.inbounds.len(), 1); + assert_eq!(config.inbounds[0].inbound_type, "mixed"); + assert_eq!(config.inbounds[0].listen, "127.0.0.1"); + assert_eq!(config.inbounds[0].listen_port, 1080); + assert!(!config.inbounds[0].set_system_proxy); + assert_eq!(config.outbounds[0].outbound_type, "direct"); + assert_eq!(config.route.final_outbound, "direct"); + let calls = checker.calls.borrow(); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].0.as_path(), binary_path); + assert!(calls[0].1.contains(r#""type": "mixed""#)); +} + +#[test] +fn skips_singbox_check_when_binary_path_is_not_supplied() { + let adapter = SingBoxAdapter::default(); + let targets = vec![local_singbox_target()]; + let components = vec![running_singbox_component()]; + let checker = RecordingChecker::ok("should not run"); + + let generated = adapter + .generate_config( + SingBoxGenerationRequest::new(&targets, &components, None), + &checker, + ) + .expect("binary path is optional"); + + assert_eq!(generated.check, None); + assert!(checker.calls.borrow().is_empty()); +} + +#[test] +fn blocks_local_singbox_config_when_required_component_is_missing() { + let adapter = SingBoxAdapter::default(); + let targets = vec![local_singbox_target()]; + let components = Vec::new(); + let checker = RecordingChecker::ok("should not run"); + + let error = adapter + .generate_config( + SingBoxGenerationRequest::new(&targets, &components, None), + &checker, + ) + .expect_err("local sing-box target requires component state"); + + assert_eq!(error.kind, SingBoxConfigErrorKind::MissingRequiredComponent); + assert!(checker.calls.borrow().is_empty()); +} + +#[test] +fn blocks_local_singbox_config_when_component_is_not_running() { + let adapter = SingBoxAdapter::default(); + let targets = vec![local_singbox_target()]; + let components = vec![stopped_singbox_component()]; + let checker = RecordingChecker::ok("should not run"); + + let error = adapter + .generate_config( + SingBoxGenerationRequest::new(&targets, &components, None), + &checker, + ) + .expect_err("local sing-box target requires running component"); + + assert_eq!(error.kind, SingBoxConfigErrorKind::RequiredComponentNotRunning); + assert!(checker.calls.borrow().is_empty()); +} + +#[test] +fn propagates_failed_singbox_check_as_structured_error() { + let adapter = SingBoxAdapter::default(); + let targets = vec![local_singbox_target()]; + let components = vec![running_singbox_component()]; + let checker = RecordingChecker::err("invalid config"); + + let error = adapter + .generate_config( + SingBoxGenerationRequest::new( + &targets, + &components, + Some(Path::new("sing-box.exe")), + ), + &checker, + ) + .expect_err("failed sing-box check should block generated config"); + + assert_eq!(error.kind, SingBoxConfigErrorKind::CheckFailed); + assert!(error.message.contains("invalid config")); +} + +#[test] +fn external_proxifyre_apply_does_not_require_singbox_component() { + let adapter = ProxiFyreAdapter::default(); + let profiles = vec![discord_profile("home-gateway")]; + let targets = vec![external_socks5_target()]; + let components = vec![missing_singbox_component()]; + + let generated = adapter + .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) + .expect("external SOCKS5 target should not require local sing-box"); + let config: ProxiFyreConfig = + serde_json::from_str(&generated.contents).expect("generated proxifyre json"); + + assert_eq!(config.proxies.len(), 1); + assert_eq!(config.proxies[0].socks5_proxy_endpoint, "192.168.50.111:8080"); +} + +struct RecordingChecker { + calls: RefCell>, + result: Result, +} + +impl RecordingChecker { + fn ok(message: &str) -> Self { + Self { + calls: RefCell::new(Vec::new()), + result: Ok(SingBoxCheckResult { + checked: true, + success: true, + message: message.to_string(), + }), + } + } + + fn err(message: &str) -> Self { + Self { + calls: RefCell::new(Vec::new()), + result: Err(SingBoxConfigError::new( + SingBoxConfigErrorKind::CheckFailed, + message, + )), + } + } +} + +impl SingBoxConfigChecker for RecordingChecker { + fn check_config( + &self, + binary_path: &Path, + config_json: &str, + ) -> Result { + self.calls + .borrow_mut() + .push((binary_path.to_path_buf(), config_json.to_string())); + self.result.clone() + } +} + +fn discord_profile(target_id: &str) -> Profile { + Profile { + id: "discord".to_string(), + name: "Discord".to_string(), + enabled: true, + target_id: target_id.to_string(), + protocols: vec![Protocol::Tcp, Protocol::Udp], + items: vec![ProfileItem { + item_type: ProfileItemType::Process, + value: "Discord".to_string(), + recursive: false, + }], + } +} + +fn external_socks5_target() -> Target { + Target { + id: "home-gateway".to_string(), + name: "Home Gateway".to_string(), + kind: TargetKind::External, + protocol: ProxyProtocol::Socks5, + host: "192.168.50.111".to_string(), + port: 8080, + requires_component: None, + } +} + +fn local_singbox_target() -> Target { + Target { + id: "local-singbox".to_string(), + name: "Local sing-box".to_string(), + kind: TargetKind::Local, + protocol: ProxyProtocol::Socks5, + host: "127.0.0.1".to_string(), + port: 1080, + requires_component: Some(ComponentId::Singbox), + } +} + +fn running_singbox_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Local sing-box".to_string(), + state: ComponentState::Running, + installed: true, + running: true, + version: Some("1.11.0".to_string()), + path: Some(r"C:\Tools\VpnProxy\sing-box\sing-box.exe".to_string()), + problems: Vec::new(), + actions: vec!["Restart".to_string(), "Stop".to_string()], + } +} + +fn stopped_singbox_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Local sing-box".to_string(), + state: ComponentState::Stopped, + installed: true, + running: false, + version: Some("1.11.0".to_string()), + path: Some(r"C:\Tools\VpnProxy\sing-box\sing-box.exe".to_string()), + problems: vec!["Service is stopped".to_string()], + actions: vec!["Start".to_string()], + } +} + +fn missing_singbox_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Singbox, + name: "Local sing-box".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["Local sing-box is not installed".to_string()], + actions: vec!["Install Local sing-box".to_string()], + } +} diff --git a/apps/windows-client/src-tauri/tests/storage_tests.rs b/apps/windows-client/src-tauri/tests/storage_tests.rs new file mode 100644 index 0000000..a088c83 --- /dev/null +++ b/apps/windows-client/src-tauri/tests/storage_tests.rs @@ -0,0 +1,195 @@ +#[path = "../src/activity.rs"] +mod activity; +#[path = "../src/models.rs"] +mod models; +#[path = "../src/storage.rs"] +mod storage; + +use models::{ + ActivityEntry, ActivityLevel, ComponentId, ComponentState, ComponentStatus, Profile, + ProfileItem, ProfileItemType, Protocol, ProxyProtocol, Target, TargetKind, +}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; +use storage::{backup_path, default_config_root, JsonStorage, StoragePaths}; + +#[test] +fn storage_defaults_to_programdata_root() { + let expected = PathBuf::from(r"C:\ProgramData\VpnProxy"); + + assert_eq!(default_config_root(), expected); + assert_eq!(StoragePaths::default().root, expected); +} + +#[test] +fn roundtrips_profiles_targets_components_and_activity() { + let root = test_root("roundtrip"); + let storage = JsonStorage::new(root.clone()); + + let profiles = vec![sample_profile("discord")]; + let targets = vec![sample_target("home-gateway")]; + let components = vec![sample_component()]; + let activity = vec![sample_activity( + "created", + "2026-01-01T10:00:00Z", + ActivityLevel::Success, + )]; + + storage.write_profiles(&profiles).expect("write profiles"); + storage.write_targets(&targets).expect("write targets"); + storage + .write_components(&components) + .expect("write components"); + storage.write_activity(&activity).expect("write activity"); + + assert_eq!(storage.read_profiles().expect("read profiles"), profiles); + assert_eq!(storage.read_targets().expect("read targets"), targets); + assert_eq!(storage.read_components().expect("read components"), components); + assert_eq!(storage.read_activity().expect("read activity"), activity); + + cleanup(&root); +} + +#[test] +fn invalid_json_falls_back_to_empty_collection() { + let root = test_root("invalid-json"); + let storage = JsonStorage::new(root.clone()); + storage.ensure_dirs().expect("create storage dirs"); + fs::write(&storage.paths().profiles_file, "{not valid json").expect("write invalid json"); + + assert_eq!( + storage.read_profiles().expect("invalid profiles fallback"), + Vec::::new() + ); + + cleanup(&root); +} + +#[test] +fn write_creates_backup_before_overwriting_source_file() { + let root = test_root("backup"); + let storage = JsonStorage::new(root.clone()); + let first = vec![sample_profile("first")]; + let second = vec![sample_profile("second")]; + + storage.write_profiles(&first).expect("first write"); + storage.write_profiles(&second).expect("second write"); + + let backup = backup_path(&storage.paths().profiles_file); + assert!(backup.exists(), "backup file should exist"); + + let backup_contents = fs::read_to_string(backup).expect("read backup"); + let backup_profiles: Vec = + serde_json::from_str(&backup_contents).expect("backup json"); + + assert_eq!(backup_profiles, first); + assert_eq!(storage.read_profiles().expect("current profiles"), second); + + cleanup(&root); +} + +#[test] +fn activity_entries_are_sorted_and_capped() { + let root = test_root("activity"); + let storage = JsonStorage::with_activity_limit(root.clone(), 2); + + storage + .append_activity(sample_activity( + "old", + "2026-01-01T10:00:00Z", + ActivityLevel::Info, + )) + .expect("append old"); + storage + .append_activity(sample_activity( + "new", + "2026-01-03T10:00:00Z", + ActivityLevel::Success, + )) + .expect("append new"); + storage + .append_activity(sample_activity( + "middle", + "2026-01-02T10:00:00Z", + ActivityLevel::Warning, + )) + .expect("append middle"); + + let entries = storage.read_activity().expect("read capped activity"); + + assert_eq!(entries.len(), 2); + assert_eq!( + entries + .iter() + .map(|entry| entry.id.as_str()) + .collect::>(), + vec!["new", "middle"] + ); + + cleanup(&root); +} + +fn test_root(name: &str) -> PathBuf { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock before unix epoch") + .as_nanos(); + + std::env::temp_dir().join(format!("vpn-proxy-storage-{name}-{timestamp}")) +} + +fn cleanup(root: &Path) { + let _ = fs::remove_dir_all(root); +} + +fn sample_profile(id: &str) -> Profile { + Profile { + id: id.to_string(), + name: format!("Profile {id}"), + enabled: true, + target_id: "home-gateway".to_string(), + protocols: vec![Protocol::Tcp, Protocol::Udp], + items: vec![ProfileItem { + item_type: ProfileItemType::Process, + value: "Discord".to_string(), + recursive: false, + }], + } +} + +fn sample_target(id: &str) -> Target { + Target { + id: id.to_string(), + name: "Home Gateway".to_string(), + kind: TargetKind::External, + protocol: ProxyProtocol::Socks5, + host: "192.168.50.111".to_string(), + port: 8080, + requires_component: None, + } +} + +fn sample_component() -> ComponentStatus { + ComponentStatus { + id: ComponentId::Proxyfier, + name: "ProxiFyre".to_string(), + state: ComponentState::Missing, + installed: false, + running: false, + version: None, + path: None, + problems: vec!["ProxiFyre не установлен".to_string()], + actions: vec!["Установить ProxiFyre".to_string()], + } +} + +fn sample_activity(id: &str, at: &str, level: ActivityLevel) -> ActivityEntry { + ActivityEntry { + id: id.to_string(), + at: at.to_string(), + level, + title: format!("Activity {id}"), + message: "Storage test activity".to_string(), + } +} diff --git a/apps/windows-client/src/api/tauriCommands.ts b/apps/windows-client/src/api/tauriCommands.ts new file mode 100644 index 0000000..43a5698 --- /dev/null +++ b/apps/windows-client/src/api/tauriCommands.ts @@ -0,0 +1,79 @@ +import { invoke } from '@tauri-apps/api/core'; +import type { + ActivityEntry, + ComponentStatus, + Profile, + ProfileInput, + Target, + TargetInput, +} from '../domain/types'; + +export interface CommandError { + code: string; + message: string; + details?: Array<{ + field: string; + message: string; + }>; +} + +export interface StatusResponse { + routeLine: string; + activeProfileCount: number; + routedAppCount: number; + activeTarget?: Target; + components: ComponentStatus[]; + recentActivity: ActivityEntry[]; + generatedConfigPath: string; +} + +export interface HelperApplyResult { + success: boolean; + changed: boolean; + action: string; + message: string; +} + +export interface ApplyProfilesResponse { + success: boolean; + changed: boolean; + message: string; + adapterId: string; + generatedConfigPath: string; + enabledProfiles: number; + routedApps: number; + helper: HelperApplyResult; + activity: ActivityEntry; +} + +export function getStatus(): Promise { + return invoke('get_status'); +} + +export function getProfiles(): Promise { + return invoke('get_profiles'); +} + +export function saveProfile(input: ProfileInput): Promise { + return invoke('save_profile', { input }); +} + +export function getTargets(): Promise { + return invoke('get_targets'); +} + +export function saveTarget(input: TargetInput): Promise { + return invoke('save_target', { input }); +} + +export function getComponents(): Promise { + return invoke('get_components'); +} + +export function applyProfiles(): Promise { + return invoke('apply_profiles'); +} + +export function openConfigLocation(): Promise { + return invoke('open_config_location'); +} diff --git a/apps/windows-client/src/app/App.tsx b/apps/windows-client/src/app/App.tsx new file mode 100644 index 0000000..3075343 --- /dev/null +++ b/apps/windows-client/src/app/App.tsx @@ -0,0 +1,462 @@ +import { useEffect, useMemo, useState } from 'react'; +import { + applyProfiles, + getComponents, + getProfiles, + getStatus, + getTargets, + openConfigLocation, + saveProfile, + saveTarget, + type ApplyProfilesResponse, +} from '../api/tauriCommands'; +import type { ComponentStatus, Profile, ProfileItemInput, ProfileItemType, Target } from '../domain/types'; + +type DraftItemType = Extract; + +interface DraftItem { + id: string; + type: DraftItemType; + value: string; +} + +interface Notice { + kind: 'success' | 'error' | 'info'; + title: string; + text: string; +} + +const MAIN_TARGET_ID = 'main-proxy'; +const MAIN_PROFILE_ID = 'main-profile'; + +const fallbackComponents: ComponentStatus[] = [ + { + id: 'proxyfier', + name: 'ProxiFyre', + state: 'missing', + installed: false, + running: false, + problems: ['ProxiFyre не найден'], + actions: [], + }, +]; + +export function App() { + const [proxyInput, setProxyInput] = useState(''); + const [profileId, setProfileId] = useState(MAIN_PROFILE_ID); + const [targetId, setTargetId] = useState(MAIN_TARGET_ID); + const [items, setItems] = useState([]); + const [loadedProfiles, setLoadedProfiles] = useState([]); + const [newItemType, setNewItemType] = useState('process'); + const [newItemValue, setNewItemValue] = useState(''); + const [components, setComponents] = useState(fallbackComponents); + const [generatedConfigPath, setGeneratedConfigPath] = useState(''); + const [notice, setNotice] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isApplying, setIsApplying] = useState(false); + const [isOpeningConfig, setIsOpeningConfig] = useState(false); + + const proxyfier = useMemo( + () => components.find((component) => component.id === 'proxyfier'), + [components], + ); + + useEffect(() => { + void refresh(); + }, []); + + async function refresh() { + setIsLoading(true); + try { + const [status, profiles, targets, detectedComponents] = await Promise.all([ + getStatus(), + getProfiles(), + getTargets(), + getComponents(), + ]); + const activeProfiles = profiles.filter((profile) => profile.enabled); + const mainProfile = profiles.find((profile) => profile.id === MAIN_PROFILE_ID); + const activeProfile = mainProfile ?? activeProfiles[0]; + const activeTarget = targetForUi(targets, status.activeTarget, activeProfile); + const editableProfiles = mainProfile ? [mainProfile] : activeProfiles; + + if (activeTarget) setProxyInput(formatProxy(activeTarget)); + setItems(itemsForProfiles(editableProfiles)); + setLoadedProfiles(profiles); + setProfileId(mainProfile?.id ?? MAIN_PROFILE_ID); + setTargetId(activeTarget?.id ?? activeProfile?.targetId ?? MAIN_TARGET_ID); + + setComponents(detectedComponents); + setGeneratedConfigPath(status.generatedConfigPath); + setNotice(null); + } catch { + setNotice({ + kind: 'info', + title: 'Режим предпросмотра', + text: 'Запусти приложение через Tauri, чтобы увидеть найденный ProxiFyre и применить конфиг.', + }); + } finally { + setIsLoading(false); + } + } + + function addItem() { + const value = normalizeItemValue(newItemValue, newItemType); + if (!value) { + setNotice({ + kind: 'error', + title: 'Нечего добавить', + text: newItemType === 'process' ? 'Введи имя процесса.' : 'Введи путь к EXE-файлу.', + }); + return; + } + + if (items.some((item) => item.type === newItemType && sameValue(item.value, value))) { + setNotice({ + kind: 'info', + title: 'Уже добавлено', + text: value, + }); + return; + } + + setItems((current) => [ + ...current, + { + id: `${newItemType}-${Date.now()}`, + type: newItemType, + value, + }, + ]); + setNewItemValue(''); + setNotice(null); + } + + function removeItem(id: string) { + setItems((current) => current.filter((item) => item.id !== id)); + } + + async function updateConfig() { + let parsedProxy: ParsedProxy; + try { + parsedProxy = parseProxy(proxyInput); + if (!items.length) { + throw new Error('Добавь хотя бы один процесс или EXE-файл.'); + } + } catch (error) { + setNotice({ + kind: 'error', + title: 'Проверь данные', + text: errorMessage(error), + }); + return; + } + + setIsApplying(true); + try { + await saveTarget({ + id: targetId, + name: 'Основной прокси', + kind: 'external', + protocol: parsedProxy.protocol, + host: parsedProxy.host, + port: parsedProxy.port, + }); + await saveProfile({ + id: profileId, + name: 'Приложения через прокси', + enabled: true, + targetId, + protocols: ['TCP', 'UDP'], + items: items.map(profileItemInput), + }); + await Promise.all( + loadedProfiles + .filter((profile) => profile.enabled && profile.id !== profileId) + .map((profile) => saveProfile(profileInputFromProfile(profile, false))), + ); + + const result = await applyProfiles(); + const [status, detectedComponents, profiles] = await Promise.all([ + getStatus(), + getComponents(), + getProfiles(), + ]); + + setComponents(detectedComponents); + setGeneratedConfigPath(status.generatedConfigPath); + setLoadedProfiles(profiles); + setNotice(noticeFromApply(result)); + } catch (error) { + setNotice({ + kind: 'error', + title: 'Конфиг не обновлен', + text: errorMessage(error), + }); + } finally { + setIsApplying(false); + } + } + + async function openConfig() { + setIsOpeningConfig(true); + try { + const openedPath = await openConfigLocation(); + setNotice({ + kind: 'info', + title: 'Конфиг открыт', + text: openedPath, + }); + } catch (error) { + setNotice({ + kind: 'error', + title: 'Не удалось открыть конфиг', + text: errorMessage(error), + }); + } finally { + setIsOpeningConfig(false); + } + } + + return ( +
+
+
+
+ VPN Proxy +

Прокси для приложений

+
+ +
+ +
+ +
+ {proxyfierTitle(proxyfier)} + {proxyfierDetails(proxyfier)} +
+
+ + + +
+
+

Приложения

+ {items.length} +
+ +
+ + setNewItemValue(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') addItem(); + }} + placeholder={newItemType === 'process' ? 'Discord' : 'C:\\Apps\\app.exe'} + spellCheck={false} + /> + +
+ +
+ {items.length ? ( + items.map((item) => ( +
+
+ {item.value} + {item.type === 'process' ? 'процесс' : 'EXE-файл'} +
+ +
+ )) + ) : ( +
Добавь процесс или путь к EXE-файлу.
+ )} +
+
+ + {notice ? ( +
+ {notice.title} + {notice.text} +
+ ) : null} + +
+ + +
+ + {generatedConfigPath ?

{generatedConfigPath}

: null} +
+
+ ); +} + +interface ParsedProxy { + protocol: 'socks5'; + host: string; + port: number; +} + +function parseProxy(rawValue: string): ParsedProxy { + const value = rawValue.trim(); + if (!value) throw new Error('Введи адрес прокси.'); + + const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `socks5://${value}`; + let parsed: URL; + try { + parsed = new URL(withProtocol); + } catch { + throw new Error('Формат: socks5://host:port или host:port.'); + } + + const protocol = parsed.protocol.replace(':', '').toLowerCase(); + if (protocol !== 'socks5') { + throw new Error('Сейчас поддерживается только SOCKS5.'); + } + if (parsed.username || parsed.password) { + throw new Error('Прокси с логином и паролем пока не поддерживаются.'); + } + + const host = parsed.hostname.replace(/^\[|\]$/g, ''); + const port = Number(parsed.port); + if (!host || !Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error('Укажи хост и порт прокси.'); + } + + return { protocol: 'socks5', host, port }; +} + +function targetForUi(targets: Target[], activeTarget: Target | undefined, profile: Profile | undefined) { + if (activeTarget) return activeTarget; + if (profile) return targets.find((target) => target.id === profile.targetId); + return targets.find((target) => target.id === MAIN_TARGET_ID) ?? targets.find((target) => target.kind === 'external'); +} + +function itemsForProfiles(profiles: Profile[]): DraftItem[] { + const seen = new Set(); + const items: DraftItem[] = []; + + for (const profile of profiles) { + for (const item of profile.items) { + if (item.type !== 'process' && item.type !== 'exe') continue; + + const key = `${item.type}:${item.value.trim().toLowerCase()}`; + if (seen.has(key)) continue; + seen.add(key); + items.push({ + id: `${item.type}-${items.length}-${item.value}`, + type: item.type, + value: item.value, + }); + } + } + + return items; +} + +function formatProxy(target: Target) { + return target.protocol === 'socks5' + ? `${target.host}:${target.port}` + : `${target.protocol}://${target.host}:${target.port}`; +} + +function normalizeItemValue(value: string, type: DraftItemType) { + const clean = value.trim().replace(/^"|"$/g, ''); + if (!clean) return ''; + if (type === 'exe') return clean; + + return clean + .split(/[\\/]/) + .pop() + ?.replace(/\.exe$/i, '') + .trim() ?? ''; +} + +function profileItemInput(item: DraftItem): ProfileItemInput { + return { + type: item.type, + value: item.value, + recursive: false, + }; +} + +function profileInputFromProfile(profile: Profile, enabled: boolean) { + return { + id: profile.id, + name: profile.name, + enabled, + targetId: profile.targetId, + protocols: profile.protocols, + items: profile.items.map((item) => ({ + type: item.type, + value: item.value, + recursive: item.recursive, + })), + }; +} + +function proxyfierTitle(component: ComponentStatus | undefined) { + if (!component) return 'ProxiFyre не проверен'; + if (component.running) return 'ProxiFyre найден и запущен'; + if (component.installed) return 'ProxiFyre найден'; + return 'ProxiFyre не найден'; +} + +function proxyfierDetails(component: ComponentStatus | undefined) { + if (!component) return 'Нажми «Обновить», чтобы проверить компьютер.'; + if (component.path) return component.path; + return component.problems[0] ?? 'Путь установки не найден.'; +} + +function noticeFromApply(result: ApplyProfilesResponse): Notice { + return { + kind: result.success ? 'success' : 'error', + title: result.success ? 'Конфиг обновлен' : 'Конфиг создан, но не применен', + text: result.message, + }; +} + +function sameValue(left: string, right: string) { + return left.trim().toLowerCase() === right.trim().toLowerCase(); +} + +function errorMessage(error: unknown) { + if (error instanceof Error) return error.message; + if (typeof error === 'string') return error; + if (error && typeof error === 'object' && 'message' in error) { + return String((error as { message: unknown }).message); + } + return 'Неизвестная ошибка.'; +} diff --git a/apps/windows-client/src/domain/types.ts b/apps/windows-client/src/domain/types.ts new file mode 100644 index 0000000..bc90138 --- /dev/null +++ b/apps/windows-client/src/domain/types.ts @@ -0,0 +1,77 @@ +export type Protocol = 'TCP' | 'UDP'; +export type ProfileItemType = 'process' | 'folder' | 'exe'; +export type TargetKind = 'local' | 'external'; +export type ProxyProtocol = 'socks5' | 'http'; +export type ComponentId = 'control-app' | 'proxyfier' | 'singbox'; +export type ComponentState = 'installed' | 'missing' | 'stopped' | 'running' | 'error'; +export type ActivityLevel = 'info' | 'warning' | 'error' | 'success'; + +export interface ProfileItemInput { + type: ProfileItemType | string; + value: string; + recursive?: boolean; +} + +export interface ProfileInput { + id?: string; + name: string; + enabled?: boolean; + targetId?: string; + protocols?: string[]; + items?: ProfileItemInput[]; +} + +export interface ProfileItem { + type: ProfileItemType; + value: string; + recursive: boolean; +} + +export interface Profile { + id: string; + name: string; + enabled: boolean; + targetId: string; + protocols: Protocol[]; + items: ProfileItem[]; +} + +export interface TargetInput { + id?: string; + name: string; + kind?: TargetKind | string; + protocol?: ProxyProtocol | string; + host: string; + port: number; + requiresComponent?: ComponentId | string; +} + +export interface Target { + id: string; + name: string; + kind: TargetKind; + protocol: ProxyProtocol; + host: string; + port: number; + requiresComponent?: ComponentId; +} + +export interface ComponentStatus { + id: ComponentId; + name: string; + state: ComponentState; + installed: boolean; + running: boolean; + version?: string; + path?: string; + problems: string[]; + actions: string[]; +} + +export interface ActivityEntry { + id: string; + at: string; + level: ActivityLevel; + title: string; + message: string; +} diff --git a/apps/windows-client/src/main.tsx b/apps/windows-client/src/main.tsx new file mode 100644 index 0000000..40916e5 --- /dev/null +++ b/apps/windows-client/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './app/App'; +import './styles/app.css'; + +createRoot(document.getElementById('root') as HTMLElement).render( + + + , +); + diff --git a/apps/windows-client/src/styles/app.css b/apps/windows-client/src/styles/app.css new file mode 100644 index 0000000..05b027d --- /dev/null +++ b/apps/windows-client/src/styles/app.css @@ -0,0 +1,325 @@ +:root { + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + color: #e5e7eb; + background: #101216; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: #101216; +} + +button, +input, +select { + font: inherit; +} + +button { + border: 0; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.56; +} + +.simple-shell { + display: block; + min-height: 100vh; + background: #101216; + padding: 0; +} + +.simple-panel { + display: grid; + align-content: start; + min-height: 100vh; + width: 100%; + border: 0; + border-radius: 0; + background: #101216; + box-shadow: none; + padding: 18px; +} + +.simple-header, +.finder-card, +.section-head, +.add-line, +.app-row, +.notice-line { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.simple-header { + margin: -18px -18px 16px; + border-bottom: 1px solid #2a2f3a; + background: #181b22; + padding: 14px 18px; +} + +.simple-header small, +.simple-field span, +.app-row span, +.config-path, +.finder-card span { + color: #8d99ae; +} + +.simple-header h1, +.section-head h2 { + margin: 0; + letter-spacing: 0; +} + +.simple-header h1 { + margin-top: 4px; + font-size: 22px; + line-height: 1.1; + font-weight: 650; +} + +.section-head h2 { + font-size: 16px; +} + +.ghost-button, +.add-line button, +.app-row button, +.open-config-button { + min-height: 36px; + border: 1px solid #343b49; + border-radius: 4px; + background: #242a35; + color: #eef2ff; + padding: 8px 12px; + cursor: pointer; +} + +.ghost-button:hover, +.add-line button:hover, +.app-row button:hover, +.open-config-button:hover { + background: #2d3543; +} + +.finder-card { + justify-content: flex-start; + min-height: 56px; + border: 1px solid #2b3342; + border-radius: 4px; + background: #151923; + padding: 12px; +} + +.finder-card > div, +.app-row > div { + min-width: 0; +} + +.finder-card strong, +.finder-card span, +.app-row strong, +.app-row span { + display: block; + overflow-wrap: anywhere; +} + +.status-light { + flex: 0 0 auto; + width: 11px; + height: 11px; + border-radius: 999px; + background: #ef4444; +} + +.finder-card.found .status-light { + background: #22c55e; + box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.12); +} + +.finder-card.missing .status-light { + background: #f59e0b; + box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12); +} + +.simple-field { + display: grid; + gap: 7px; + margin: 14px 0; +} + +.simple-field input, +.add-line input, +.add-line select { + min-height: 42px; + width: 100%; + border: 1px solid #343b49; + border-radius: 4px; + background: #0d1016; + color: #f8fafc; + outline: none; + padding: 9px 11px; +} + +.simple-field input:focus, +.add-line input:focus, +.add-line select:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 1px #3b82f6; +} + +.apps-section { + display: grid; + gap: 10px; + margin-top: 8px; +} + +.section-head span { + min-width: 28px; + border: 1px solid #343b49; + border-radius: 4px; + background: #1b202b; + color: #dbeafe; + padding: 3px 9px; + text-align: center; + font-size: 12px; + font-weight: 700; +} + +.add-line { + display: grid; + grid-template-columns: 130px minmax(0, 1fr) auto; +} + +.add-line button { + min-width: 104px; + border-color: #166534; + background: #14532d; +} + +.add-line button:hover { + background: #166534; +} + +.app-list { + display: grid; + gap: 8px; +} + +.app-row, +.empty-state, +.notice-line { + border: 1px solid #2b3342; + border-radius: 4px; + background: #131720; + padding: 10px 12px; +} + +.app-row button { + color: #fecaca; +} + +.empty-state { + color: #8d99ae; + min-height: 48px; +} + +.notice-line { + align-items: flex-start; + justify-content: flex-start; + margin-top: 12px; +} + +.notice-line strong, +.notice-line span { + display: block; +} + +.notice-line.success { + border-color: rgba(34, 197, 94, 0.42); + background: rgba(20, 83, 45, 0.32); +} + +.notice-line.error { + border-color: rgba(239, 68, 68, 0.42); + background: rgba(127, 29, 29, 0.32); +} + +.notice-line.info { + border-color: rgba(59, 130, 246, 0.42); + background: rgba(30, 58, 138, 0.26); +} + +.command-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 132px; + gap: 8px; + margin-top: 14px; +} + +.apply-button { + min-height: 46px; + width: 100%; + border: 1px solid #16a34a; + border-radius: 4px; + background: #22c55e; + color: #04130a; + font-weight: 800; + cursor: pointer; +} + +.apply-button:hover { + background: #4ade80; +} + +.open-config-button { + min-height: 46px; + width: 100%; +} + +.config-path { + margin: 10px 0 0; + font-size: 12px; + overflow-wrap: anywhere; +} + +@media (max-width: 680px) { + .simple-shell { + padding: 0; + } + + .simple-panel { + padding: 14px; + } + + .simple-header, + .app-row, + .notice-line { + align-items: stretch; + flex-direction: column; + } + + .add-line { + grid-template-columns: 1fr; + } + + .command-row { + grid-template-columns: 1fr; + } +} diff --git a/apps/windows-client/tsconfig.json b/apps/windows-client/tsconfig.json new file mode 100644 index 0000000..dde57bf --- /dev/null +++ b/apps/windows-client/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [] +} + diff --git a/apps/windows-client/vite.config.ts b/apps/windows-client/vite.config.ts new file mode 100644 index 0000000..9da719c --- /dev/null +++ b/apps/windows-client/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +const host = process.env.TAURI_DEV_HOST; + +export default defineConfig({ + plugins: [react()], + clearScreen: false, + server: { + host: host || false, + port: 5173, + strictPort: true, + watch: { + ignored: ['**/src-tauri/**'], + }, + }, +}); + diff --git a/docs/goals/windows-modular-client/EVIDENCE.md b/docs/goals/windows-modular-client/EVIDENCE.md new file mode 100644 index 0000000..7e30d61 --- /dev/null +++ b/docs/goals/windows-modular-client/EVIDENCE.md @@ -0,0 +1,1342 @@ +# Windows Tauri Proxy Client Evidence + +## Acceptance Evidence + +Record target-perspective proof: + +- app screenshot or state payload; +- generated ProxiFyre config artifact; +- helper/apply response; +- component status showing missing/installed states; +- Windows manual checklist results. + +### Task 1: Supersede Old Windows Node Plan + +Accepted evidence for this checkpoint: + +- `README.md` now states that Windows app routing is planned as a separate Tauri 2 desktop utility, not `APP_MODE=windows` inside the current Node gateway/client server. +- `docs/roadmap.md` now lists `windows-gaming` as a standalone Tauri 2 app direction and points to this execution plan. +- `docs/superpowers/specs/2026-05-21-windows-client-design.md` is marked superseded and points to the product/tech brief plus this plan. +- `docs/superpowers/plans/2026-05-21-windows-client.md` is marked superseded and says not to execute the old Node `APP_MODE=windows` plan. +- Both old Windows docs now state that the content below is historical context and may contradict the active Tauri plan. + +Product-level app evidence is not expected for Task 1 because this task is documentation cutover only. + +### Task 2: Scaffold Tauri App Shell + +Accepted evidence for this checkpoint: + +- `apps/windows-client` now contains a standalone Tauri 2 + React + TypeScript app scaffold. +- The shell has five visible navigation surfaces: Overview, Profiles, Targets, Components, Logs. +- The shell shows the intended component split: Control App installed, Proxyfier Layer missing, Local sing-box missing/optional. +- The shell route line starts with the MVP external proxy path: `Selected apps -> Proxyfier -> Existing proxy`. +- Browser verification confirmed each navigation button renders a matching page heading. +- Native Rust/Tauri compilation is blocked in this environment because Rust, Cargo, rustup, MSVC Build Tools, and Windows SDK components are not installed. + +### Task 3: Define Domain Models And Validation + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/models.rs` now defines Rust DTO/domain models for profiles, profile items, targets, component status, and activity entries. +- `apps/windows-client/src-tauri/src/validation.rs` now normalizes profile and target inputs. +- `apps/windows-client/src/domain/types.ts` mirrors the Rust-facing DTOs for React/TypeScript callers. +- `apps/windows-client/src-tauri/tests/domain_tests.rs` contains Rust tests for process/folder/exe normalization, malformed protocol rejection, external target normalization, local sing-box target definition without installed component state, and malformed target field rejection. +- The validation design keeps Local sing-box optional: a local target can require `singbox`, but target definition validation does not require that component to be installed. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2. + +### Task 4: Implement JSON Storage And Activity Log + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/storage.rs` now defines `JsonStorage` with a default root of `C:\ProgramData\VpnProxy`. +- The storage paths split source config files under `config`, activity under `state`, and future generated artifacts under `generated`. +- Profiles, targets, components, and activity have typed JSON read/write methods. +- Writes create parent directories, write through a sibling `.tmp` file, and copy the existing source file to a sibling `.bak` file before overwrite. +- Missing files and invalid JSON fall back to empty collections instead of crashing callers. +- `apps/windows-client/src-tauri/src/activity.rs` now owns activity sorting, append, and cap behavior. +- `apps/windows-client/src-tauri/tests/storage_tests.rs` contains Rust tests for ProgramData default root, roundtrip persistence, invalid JSON fallback, backup creation, and activity cap/sort. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2, so this checkpoint is implemented but Rust-unproven in the current environment. + +### Task 5: Add Proxy Router Adapter Boundary And ProxiFyre Adapter + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/adapters/proxy_router.rs` now defines the `ProxyRouterAdapter` trait, request DTO, generated config DTO, and structured adapter errors. +- `apps/windows-client/src-tauri/src/adapters/proxifyre.rs` now defines `ProxiFyreAdapter` and typed `ProxiFyreConfig` / `ProxiFyreProxy` derived-config DTOs. +- The ProxiFyre generated JSON follows the official ProxiFyre `app-config.json` shape: `logLevel`, `bypassLan`, `proxies`, `appNames`, `socks5ProxyEndpoint`, and `supportedProtocols`. +- Enabled profiles generate ProxiFyre proxy entries from source profile items and targets; disabled profiles are skipped. +- External SOCKS5 targets do not require Local sing-box component state. +- Targets that require a component, such as `local-singbox` requiring `singbox`, are blocked unless the component is installed and running. +- HTTP targets are rejected by the ProxiFyre adapter because ProxiFyre is a SOCKS5 proxy-router backend. +- `apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs` contains tests for Discord + external SOCKS5 config generation, disabled profile skipping, missing local sing-box blocking, running local sing-box allowing generation, and HTTP rejection. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2, so this checkpoint is implemented but Rust-unproven in the current environment. + +### Task 6: Add Tauri Commands + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/commands.rs` now defines structured Tauri command DTOs and handlers for status, profiles, targets, components, resolve preview, apply, and logs. +- `apps/windows-client/src-tauri/src/main.rs` now registers the Task 6 commands through `tauri::generate_handler!`. +- `apps/windows-client/src/api/tauriCommands.ts` now exposes typed async `invoke(...)` wrappers for `get_status`, `get_profiles`, `save_profile`, `get_targets`, `save_target`, `get_components`, `resolve_profile_preview`, `apply_profiles`, and `get_logs`. +- Command responses use structured JSON DTOs and camelCase fields for the TypeScript boundary. +- Save profile/target command services normalize inputs through the existing Rust validation layer before writing JSON source files. +- Apply uses the `ProxyRouterAdapter` boundary, writes the generated ProxiFyre config artifact under the configured generated directory, calls a helper trait, and records success activity. +- Adapter-blocked apply, such as a `local-singbox` target with missing/stopped sing-box, records an error activity entry before returning a structured command error. +- `apps/windows-client/src-tauri/tests/command_tests.rs` contains Rust tests for save normalization/persistence, resolve preview, generated config + mock helper + activity, and blocked local sing-box activity. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2, so this checkpoint is implemented but Rust-unproven in the current environment. + +### Task 7: Build MVP UI + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src/app/App.tsx` now owns MVP dashboard state, Tauri command loading, browser preview fallback, profile/target save handlers, profile preview, and apply action state. +- `apps/windows-client/src/features/overview/OverviewPage.tsx` now shows route status, active profile/app counts, active target, Proxyfier state, Local sing-box optional state, recent activity, refresh, and apply controls. +- `apps/windows-client/src/features/profiles/ProfilesPage.tsx` now provides a usable profile editor for process/folder/exe items, target selection, protocol toggles, enabled state, save, and preview. +- `apps/windows-client/src/features/targets/TargetsPage.tsx` now provides an external proxy target editor and target list. +- `apps/windows-client/src/features/components/ComponentsPage.tsx` now shows Control App, Proxyfier Layer, and Local sing-box as separate operable components with explicit actions. +- `apps/windows-client/src/features/logs/LogsPage.tsx` now shows activity entries and copy diagnostics output. +- `apps/windows-client/src/styles/app.css` now provides a responsive compact Windows utility layout with dense metrics, forms, lists, status badges, and component rail. +- The UI uses Task 6 command wrappers when running inside Tauri and a local preview fallback for browser/dev verification. + +Browser screenshot/DOM automation remains blocked by the current sandbox/browser runtime helper failure, so this checkpoint is implemented but browser-unproven in the current environment. + +### Task 8: Implement Helper And Explicit Installer Boundary + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/helper.rs` now defines structured helper requests/responses, action names, command specs, command runner abstraction, JSON parsing, explicit install requests, service requests, and ProxiFyre apply request construction. +- Helper actions use machine-readable names such as `install-control-app`, `install-proxyfier`, `install-singbox`, `proxyfier.apply`, and `service.restart`. +- Helper parsing rejects non-JSON stdout, preserving the rule that app logic does not parse raw PowerShell/stdout text. +- `apps/windows-client/src-tauri/tests/helper_tests.rs` contains mock-runner tests for JSON stdin, structured response parsing, elevation flags, explicit install actions, ProxiFyre apply not encoding install, non-JSON rejection, and failed helper exit errors. +- `apps/windows-client/scripts/install-control-app.ps1`, `install-proxyfier.ps1`, and `install-singbox.ps1` are explicit component installer entrypoints. +- Installer scripts are idempotent boundaries: they create marker state only when needed, expose `-PlanOnly`, check admin before install work, return JSON, and backup existing component config files before overwrite where applicable. +- `apps/windows-client/src-tauri/capabilities/default.json` remains narrow with only `core:default`; its description documents that no shell/sidecar helper launch permission is granted until a packaged helper is declared. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2, so this checkpoint is implemented but Rust-unproven in the current environment. + +### Task 9: Add Optional Local Sing-Box Adapter + +Accepted evidence for this checkpoint: + +- `apps/windows-client/src-tauri/src/adapters/singbox.rs` now defines a separate `SingBoxAdapter` for generated local sing-box config. +- The generated config uses a local `mixed` inbound on the configured `local-singbox` target host/port, a direct outbound placeholder, and `route.final` pointing at that outbound. +- `SingBoxGenerationRequest` accepts an optional binary path; `SingBoxCommandChecker` writes a temporary config and runs `sing-box check -c ` only when a binary path is supplied. +- Local sing-box config generation requires a target that is local, SOCKS5, and declares `requires_component: singbox`. +- Local sing-box config generation is blocked unless the `singbox` component is installed, running, and in `ComponentState::Running`. +- `apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs` contains tests for config generation, optional check skipping, failed check propagation, missing/stopped sing-box blocking, and external ProxiFyre generation without sing-box. +- `apps/windows-client/src/features/targets/TargetsPage.tsx` now renders the local sing-box target as an explicit `install prompt` row instead of a normal ready target when it requires `singbox`. +- `apps/windows-client/src/features/components/ComponentsPage.tsx` now labels stopped/missing Local sing-box as optional and only needed when this PC should expose a local target. + +Rust test execution remains blocked by the missing Rust/MSVC toolchain recorded in Task 2, so this checkpoint is implemented but Rust-unproven in the current environment. + +### Task 10: Package, Verify, And Record Evidence + +Accepted evidence for this checkpoint: + +- `apps/windows-client/README.md` now documents the Windows client component split, source JSON paths, generated artifact paths, development commands, native prerequisites, explicit installer boundaries, and MVP verification flow. +- `README.md` now links the Windows client README, documents Windows client build/test commands, explains the three separate installer scripts, and records that generated ProxiFyre/sing-box files are derived artifacts. +- `docs/roadmap.md` now records the Windows client checkpoint: MVP slice exists, frontend build passes, native Rust/Tauri verification needs the Windows Rust/MSVC toolchain, and Local sing-box remains optional. +- Browser target-perspective state was captured against the Windows client dev server on `http://127.0.0.1:5174/`: title `VPN Proxy Windows`, five navigation surfaces, external SOCKS5 route line, Control App/Proxyfier/Local sing-box component rail, and no horizontal overflow. +- Browser route checks confirmed Overview, Profiles, Targets, Components, and Logs surfaces render. Targets showed Local sing-box as an `install prompt`. +- Browser preview apply produced the visible notice `proxifyre.stage-generated-config` with `Generated config staged in preview mode` and showed `C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json`. +- Installer `-PlanOnly` commands returned structured JSON for Control App, Proxyfier Layer, and Local sing-box without install side effects. +- Native Rust/Tauri build and real generated-file/helper/service behavior remain implemented but unproven in this environment because Rust/Cargo/MSVC Build Tools are not installed and no elevated Windows helper/service run was performed. +## Verification + +Record focused checks that passed, including command and important output. + +### Task 1 Verification + +Command: + +```powershell +rg -n "Tauri|superseded|Superseded|windows-client-product-tech-brief|apps/windows-client" README.md docs +``` + +Important output included: + +- `README.md:58:Windows app routing is planned as a separate Tauri 2 desktop utility` +- `docs\roadmap.md:11:standalone Tauri 2 app + ProxiFyre adapter + optional native sing-box.exe` +- `docs\roadmap.md:91:- Standalone Tauri 2 + React/TypeScript + Rust app under apps/windows-client` +- `docs\superpowers\specs\2026-05-21-windows-client-design.md:3:> Superseded: this document describes the earlier Node/web-control Windows direction.` +- `docs\superpowers\specs\2026-05-21-windows-client-design.md:7:> Content below is retained for historical context and may contradict the active` +- `docs\superpowers\plans\2026-05-21-windows-client.md:3:> Superseded: do not execute this Node APP_MODE=windows plan as the current` +- `docs\superpowers\plans\2026-05-21-windows-client.md:7:> Content below is retained for historical context and may contradict the active` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 2 Verification + +Command: + +```powershell +node --version +npm --version +cargo --version +rustc --version +``` + +Important output: + +- `node`: `v26.4.0` +- `npm`: `11.17.0` +- `cargo`: not recognized +- `rustc`: not recognized + +Command: + +```powershell +cd apps/windows-client +npm install +``` + +Important output: + +- `added 72 packages` +- `found 0 vulnerabilities` +- npm warned that `esbuild@0.28.1` has an install script not yet covered by `allowScripts`. + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `vite v7.3.6 building client environment for production` +- `36 modules transformed` +- `built in 426ms` + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- Tauri detected app config: framework `React`, bundler `Vite`, `frontendDist: ../dist`, `devUrl: http://localhost:5173/`. +- Tauri packages detected: `@tauri-apps/api 2.11.1`, `@tauri-apps/cli 2.11.4`, Rust crate `tauri: 2`. +- Environment blockers: no Visual Studio/MSVC Build Tools, `rustc` not installed, `Cargo` not installed, `rustup` not installed. + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Browser verification: + +- Dev server: `http://127.0.0.1:5173/`. +- DOM snapshot showed all five primary nav buttons and the component status rail. +- Route click checks returned `ok: true` for Overview, Profiles, Targets, Components, and Logs. +- Layout metrics at 1280px viewport: app shell grid `260px 1020px`, no horizontal overflow (`bodyClientWidth: 1280`, `bodyScrollWidth: 1280`). + +### Task 3 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `36 modules transformed` +- `built in 424ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing MSVC Build Tools, `rustc`, `Cargo`, and `rustup`. + +Command: + +```powershell +rg -n "ProfileInput|ProfileItemInput|TargetInput|ComponentStatus|ActivityEntry|normalize_profile|normalize_target|local_singbox_target_can_exist_before_component_is_installed|rejects_malformed_target_fields" apps\windows-client\src-tauri apps\windows-client\src\domain\types.ts +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\models.rs:60:pub struct ProfileInput` +- `apps\windows-client\src-tauri\src\models.rs:92:pub struct TargetInput` +- `apps\windows-client\src-tauri\src\models.rs:117:pub struct ComponentStatus` +- `apps\windows-client\src-tauri\src\models.rs:132:pub struct ActivityEntry` +- `apps\windows-client\src-tauri\src\validation.rs:109:pub fn normalize_profile` +- `apps\windows-client\src-tauri\src\validation.rs:175:pub fn normalize_target` +- `apps\windows-client\src-tauri\tests\domain_tests.rs:94:fn local_singbox_target_can_exist_before_component_is_installed` +- `apps\windows-client\src-tauri\tests\domain_tests.rs:111:fn rejects_malformed_target_fields` + +### Task 4 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `36 modules transformed` +- `built in 395ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. + +Command: + +```powershell +rg -n "default_config_root|write_profiles|read_profiles|backup_path|append_activity|activity_entries_are_sorted_and_capped|invalid_json_falls_back_to_empty_collection|roundtrips_profiles_targets_components_and_activity" apps\windows-client\src-tauri +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\storage.rs:8:pub fn default_config_root` +- `apps\windows-client\src-tauri\src\storage.rs:79:pub fn read_profiles` +- `apps\windows-client\src-tauri\src\storage.rs:83:pub fn write_profiles` +- `apps\windows-client\src-tauri\src\storage.rs:150:pub fn backup_path` +- `apps\windows-client\src-tauri\src\activity.rs:16:pub fn append_activity` +- `apps\windows-client\src-tauri\tests\storage_tests.rs:26:fn roundtrips_profiles_targets_components_and_activity` +- `apps\windows-client\src-tauri\tests\storage_tests.rs:55:fn invalid_json_falls_back_to_empty_collection` +- `apps\windows-client\src-tauri\tests\storage_tests.rs:93:fn activity_entries_are_sorted_and_capped` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 5 Verification + +External reference checked: + +- Official ProxiFyre README: `https://github.com/wiresock/proxifyre` +- Relevant config fields: `app-config.json`, `logLevel`, `bypassLan`, `proxies`, `appNames`, `socks5ProxyEndpoint`, `supportedProtocols`. + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `36 modules transformed` +- `built in 401ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. + +Command: + +```powershell +rg -n "ProxyRouterAdapter|ProxiFyreAdapter|PROXIFYRE_OUTPUT_FILE|generate_proxifyre_config|generates_proxifyre_config_for_discord_external_socks5_target|blocks_local_singbox_target_when_required_component_is_missing|rejects_http_target" apps\windows-client\src-tauri +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\adapters\proxy_router.rs:58:pub trait ProxyRouterAdapter` +- `apps\windows-client\src-tauri\src\adapters\proxifyre.rs:21:pub struct ProxiFyreAdapter` +- `apps\windows-client\src-tauri\src\adapters\proxifyre.rs:34:pub fn generate_proxifyre_config` +- `apps\windows-client\src-tauri\tests\proxifyre_adapter_tests.rs:16:fn generates_proxifyre_config_for_discord_external_socks5_target` +- `apps\windows-client\src-tauri\tests\proxifyre_adapter_tests.rs:61:fn blocks_local_singbox_target_when_required_component_is_missing` +- `apps\windows-client\src-tauri\tests\proxifyre_adapter_tests.rs:92:fn rejects_http_target_because_proxifyre_adapter_is_socks5_only` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 6 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `38 modules transformed` +- `built in 393ms` + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +rg -n "tauri::command|get_status|get_profiles|save_profile|get_targets|save_target|get_components|resolve_profile_preview|apply_profiles|get_logs|apply_generates_derived_config_and_records_activity_with_mock_helper|invoke<" apps\windows-client\src-tauri apps\windows-client\src\api\tauriCommands.ts +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\commands.rs:284:pub fn get_status` +- `apps\windows-client\src-tauri\src\commands.rs:289:pub fn get_profiles` +- `apps\windows-client\src-tauri\src\commands.rs:296:pub fn save_profile` +- `apps\windows-client\src-tauri\src\commands.rs:304:pub fn get_targets` +- `apps\windows-client\src-tauri\src\commands.rs:309:pub fn save_target` +- `apps\windows-client\src-tauri\src\commands.rs:317:pub fn get_components` +- `apps\windows-client\src-tauri\src\commands.rs:324:pub fn resolve_profile_preview` +- `apps\windows-client\src-tauri\src\commands.rs:331:pub fn apply_profiles` +- `apps\windows-client\src-tauri\src\commands.rs:343:pub fn get_logs` +- `apps\windows-client\src-tauri\tests\command_tests.rs:116:fn apply_generates_derived_config_and_records_activity_with_mock_helper` +- `apps\windows-client\src\api\tauriCommands.ts:96:return invoke('get_status')` +- `apps\windows-client\src\api\tauriCommands.ts:126:return invoke('apply_profiles')` + +Command: + +```powershell +rg -n "activity_for_apply_error|apply_blocks_local_singbox_target_when_component_is_missing|apply_generates_derived_config_and_records_activity_with_mock_helper|invoke<|generate_handler" apps\windows-client\src-tauri apps\windows-client\src\api\tauriCommands.ts +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\main.rs:17:.invoke_handler(tauri::generate_handler![` +- `apps\windows-client\src-tauri\src\commands.rs:479:let activity = activity_for_apply_error` +- `apps\windows-client\src-tauri\src\commands.rs:633:fn activity_for_apply_error` +- `apps\windows-client\src-tauri\tests\command_tests.rs:116:fn apply_generates_derived_config_and_records_activity_with_mock_helper` +- `apps\windows-client\src-tauri\tests\command_tests.rs:158:fn apply_blocks_local_singbox_target_when_component_is_missing` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 7 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `38 modules transformed` +- `built in 431ms` + +Command: + +```powershell +cd apps/windows-client +npm run dev -- --host 127.0.0.1 +``` + +Important output: + +- `VITE v7.3.6 ready in 206 ms` +- `Local: http://127.0.0.1:5173/` + +Command: + +```powershell +Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:5173/' | Select-Object StatusCode,Content +``` + +Important output: + +- `StatusCode 200` +- Response content starts with ``. + +Command: + +```powershell +rg -n "Apply changes|Save profile|Save target|External proxy first|Local sing-box|resolveProfilePreview|applyProfiles|Copy diagnostics|Preview state loaded" apps\windows-client\src\app apps\windows-client\src\features apps\windows-client\src\api\tauriCommands.ts +``` + +Important output included: + +- `apps\windows-client\src\app\App.tsx:328:const preview = await resolveProfilePreview(input)` +- `apps\windows-client\src\app\App.tsx:371:const response = await applyProfiles()` +- `apps\windows-client\src\features\overview\OverviewPage.tsx:36:

External proxy first, local sing-box optional

` +- `apps\windows-client\src\features\overview\OverviewPage.tsx:43:{isApplying ? 'Applying' : 'Apply changes'}` +- `apps\windows-client\src\features\profiles\ProfilesPage.tsx:48:Save profile` +- `apps\windows-client\src\features\targets\TargetsPage.tsx:27:Save target` +- `apps\windows-client\src\features\logs\LogsPage.tsx:29:Copy diagnostics` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +Blocked browser evidence: + +- Browser skill file listed in session context was stale; current skill was found at `C:\Users\PC\.codex\plugins\cache\openai-bundled\browser\26.623.101652\skills\control-in-app-browser\SKILL.md` and read before browser work. +- `mcp__node_repl.js` browser setup failed with `windows sandbox failed: helper_unknown_error: setup refresh had errors`. +- `apply_patch` also failed with the same sandbox helper error, so Task 7 file edits were made with explicit escalated `Set-Content` fallback against Task 7-allowed files only. + +### Task 8 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `38 modules transformed` +- `built in 404ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. + +Command: + +```powershell +$files = @( + 'apps\windows-client\scripts\install-control-app.ps1', + 'apps\windows-client\scripts\install-proxyfier.ps1', + 'apps\windows-client\scripts\install-singbox.ps1' +) +foreach ($file in $files) { + $tokens = $null + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $file), [ref]$tokens, [ref]$errors) | Out-Null + if ($errors.Count -gt 0) { Write-Error "$file parser errors" } + Write-Output "$file parser ok" +} +``` + +Important output: + +- `apps\windows-client\scripts\install-control-app.ps1 parser ok` +- `apps\windows-client\scripts\install-proxyfier.ps1 parser ok` +- `apps\windows-client\scripts\install-singbox.ps1 parser ok` + +Command: + +```powershell +& 'apps\windows-client\scripts\install-control-app.ps1' -PlanOnly +& 'apps\windows-client\scripts\install-proxyfier.ps1' -PlanOnly +& 'apps\windows-client\scripts\install-singbox.ps1' -PlanOnly +``` + +Important output: + +- Control App returned JSON with `"success": true`, `"action": "install-control-app"`, `"changed": false`, and `"planOnly": true`. +- Proxyfier returned JSON with `"success": true`, `"action": "install-proxyfier"`, `"changed": false`, `"serviceName": "ProxiFyreService"`, and `"planOnly": true`. +- Local sing-box returned JSON with `"success": true`, `"action": "install-singbox"`, `"changed": false`, `"serviceName": "VpnProxySingBox"`, and `"planOnly": true`. + +Command: + +```powershell +rg -n "proxyfier\.apply|service\.restart|runner\(\)|install_request|proxifyre_apply_request|helper_response_decode|PlanOnly|core:default|no shell or sidecar" apps\windows-client\src-tauri apps\windows-client\scripts +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\helper.rs:14:#[serde(rename = "proxyfier.apply")]` +- `apps\windows-client\src-tauri\src\helper.rs:22:#[serde(rename = "service.restart")]` +- `apps\windows-client\src-tauri\src\helper.rs:131:"helper_response_decode"` +- `apps\windows-client\src-tauri\src\helper.rs:137:pub fn install_request` +- `apps\windows-client\src-tauri\src\helper.rs:159:pub fn proxifyre_apply_request` +- `apps\windows-client\src-tauri\tests\helper_tests.rs:73:fn install_requests_are_explicit_component_actions` +- `apps\windows-client\src-tauri\capabilities\default.json:6:"permissions": ["core:default"]` +- `apps\windows-client\scripts\install-control-app.ps1:50:if ($PlanOnly)` +- `apps\windows-client\scripts\install-proxyfier.ps1:53:if ($PlanOnly)` +- `apps\windows-client\scripts\install-singbox.ps1:53:if ($PlanOnly)` + +Command: + +```powershell +Get-Content -LiteralPath 'apps\windows-client\src-tauri\capabilities\default.json' -Raw | ConvertFrom-Json | Select-Object identifier,description,permissions +``` + +Result: + +- JSON parsed successfully. +- `identifier`: `default` +- Description states that no shell or sidecar permission is granted until a packaged helper is declared. + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 9 Verification + +External references checked: + +- Official sing-box Mixed inbound docs: `https://sing-box.sagernet.org/configuration/inbound/mixed/`. +- Official sing-box Direct outbound docs: `https://sing-box.sagernet.org/configuration/outbound/direct/`. +- Official sing-box Route docs for `route.final`: `https://sing-box.sagernet.org/configuration/route/`. +- Official sing-box Log docs for log structure: `https://sing-box.sagernet.org/configuration/log/`. + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `38 modules transformed` +- `built in 469ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- App config still detected as React/Vite Tauri app. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. + +Command: + +```powershell +rg -n "SingBoxAdapter|SingBoxGenerationRequest|sing-box check|install prompt|Optional\. Install|external_proxifyre_apply_does_not_require_singbox_component" apps\windows-client\src-tauri apps\windows-client\src\features +``` + +Important output included: + +- `apps\windows-client\src-tauri\src\adapters\singbox.rs:18:pub struct SingBoxAdapter` +- `apps\windows-client\src-tauri\src\adapters\singbox.rs:39:request: SingBoxGenerationRequest<'_>` +- `apps\windows-client\src-tauri\src\adapters\singbox.rs:221:format!("sing-box check failed: {message}")` +- `apps\windows-client\src-tauri\tests\singbox_adapter_tests.rs:145:fn external_proxifyre_apply_does_not_require_singbox_component` +- `apps\windows-client\src\features\targets\TargetsPage.tsx:83:{localSingboxPrompt ? 'install prompt' : target.kind === 'local' ? 'local' : 'external'}` +- `apps\windows-client\src\features\components\ComponentsPage.tsx:18:? 'Optional. Install and start only when this PC should expose a local target.'` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. + +### Task 10 Verification + +Command: + +```powershell +cd apps/windows-client +npm run build +``` + +Important output: + +- `tsc && vite build` +- `38 modules transformed` +- `built in 428ms` + +Command: + +```powershell +cd apps/windows-client/src-tauri +cargo test +``` + +Result: + +- Blocked: `cargo` is not recognized in the current environment. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- info +``` + +Important output: + +- WebView2 detected: `149.0.4022.98`. +- Environment still reports missing Visual Studio/MSVC Build Tools, `rustc`, `Cargo`, `rustup`, and Rust toolchain. +- App config detected as React/Vite with `frontendDist: ../dist` and `devUrl: http://localhost:5173/`. + +Command: + +```powershell +cd apps/windows-client +npm run tauri -- build +``` + +Result: + +- Blocked: `failed to run 'cargo metadata' ... program not found`. + +Command: + +```powershell +$files = @( + 'apps\windows-client\scripts\install-control-app.ps1', + 'apps\windows-client\scripts\install-proxyfier.ps1', + 'apps\windows-client\scripts\install-singbox.ps1' +) +foreach ($file in $files) { + $tokens = $null + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $file), [ref]$tokens, [ref]$errors) | Out-Null + if ($errors.Count -gt 0) { Write-Error "$file parser errors" } else { Write-Output "$file parser ok" } +} +``` + +Important output: + +- `apps\windows-client\scripts\install-control-app.ps1 parser ok` +- `apps\windows-client\scripts\install-proxyfier.ps1 parser ok` +- `apps\windows-client\scripts\install-singbox.ps1 parser ok` + +Command: + +```powershell +$files = @( + 'apps\windows-client\scripts\install-control-app.ps1', + 'apps\windows-client\scripts\install-proxyfier.ps1', + 'apps\windows-client\scripts\install-singbox.ps1' +) +foreach ($file in $files) { & $file -PlanOnly } +``` + +Important output: + +- Control App returned JSON with `"success": true`, `"action": "install-control-app"`, `"changed": false`, and `"planOnly": true`. +- Proxyfier returned JSON with `"success": true`, `"action": "install-proxyfier"`, `"changed": false`, `"serviceName": "ProxiFyreService"`, and `"planOnly": true`. +- Local sing-box returned JSON with `"success": true`, `"action": "install-singbox"`, `"changed": false`, `"serviceName": "VpnProxySingBox"`, and `"planOnly": true`. + +Browser evidence: + +- Port `5173` was already occupied by an unrelated page, so the Windows client dev server was started on `http://127.0.0.1:5174/` with `--strictPort`. +- Initial app state payload included: + - `title`: `VPN Proxy Windows` + - `routeHeading`: `Overview` + - `panelHeading`: `External proxy first, local sing-box optional` + - `routeLine`: `Selected apps -> ProxiFyre -> Existing proxy 192.168.50.111:8080` + - component rail: `Control AppRunning`, `Proxyfier LayerNot installed`, `Local sing-boxNot installed` + - `horizontalOverflow`: `false` +- Route checks returned five surfaces: Overview, Profiles, Targets, Components, Logs. +- Targets route text included `Local sing-boxSOCKS5 127.0.0.1:1080Install and start Local sing-box before using this target.install prompt`. +- Preview apply on Overview produced a visible notice: `proxifyre.stage-generated-configGenerated config staged in preview mode.` +- Preview apply also kept `C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json` visible. +- Browser tab was closed after verification. The extra dev server on `5174` was stopped. The attempted process for `5173` was already not running. + +Command: + +```powershell +rg -n "Windows client README|install-control-app|install-proxyfier|install-singbox|C:\\ProgramData\\VpnProxy|Current checkpoint|npm run build|cargo test" README.md docs\roadmap.md apps\windows-client\README.md +``` + +Important output included: + +- `README.md:66:- Windows client README: apps/windows-client/README.md` +- `README.md:97:& .\scripts\install-control-app.ps1 -PlanOnly` +- `README.md:98:& .\scripts\install-proxyfier.ps1 -PlanOnly` +- `README.md:99:& .\scripts\install-singbox.ps1 -PlanOnly` +- `README.md:107:C:\ProgramData\VpnProxy\config` +- `docs\roadmap.md:89:Current checkpoint:` +- `apps\windows-client\README.md:60:cargo test` + +Command: + +```powershell +git diff --check +``` + +Result: + +- No whitespace errors reported. +- Git warned that several modified Markdown files will be normalized from LF to CRLF next time Git touches them. +## Review Notes + +Record plan-reviewer, reviewer, maintainer, or verifier findings that changed the result. + +### PRE Review + +Mode: PRE + +Verdict: aligned for Task 1. + +Findings: + +- No blocker findings. +- The plan has explicit outcome, truth owner, contract boundary, cutover, displaced path, kill criteria, and evidence requirements. +- Task 1 is documentation-only and its allowed file scope avoids runtime source files, matching the cutover requirement. + +### POST Review + +Mode: POST + +Verdict: aligned. + +Findings: + +- No blocker or major findings. +- Implementation stayed within Task 1's documentation-only file scope. +- Displaced Node `APP_MODE=windows` path is demoted in README, roadmap, and both older Windows docs. +- Acceptance evidence for this checkpoint is sufficient because Task 1 is cutover documentation, not product behavior. + +### Correctness Review + +Findings: + +- No correctness issues found for this documentation checkpoint. +- Residual risk: product behavior remains unimplemented; app/build/generated-config evidence is deferred to later tasks. + +### Maintainer Review + +Findings: + +- Minor risk found and fixed: the old Windows spec still contains historical text such as "No Electron or Tauri wrapper"; both old Windows docs now explicitly warn that the remaining content is historical and may contradict the active Tauri plan. +- No duplicate current-looking Windows implementation path remains after the added superseded notes. + +### Task 2 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 2 creates only the separate `apps/windows-client` Tauri app slice plus evidence updates. +- Current Node gateway/client runtime files remain out of scope. + +### Task 2 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- The implementation stayed within the Tauri app slice and did not extend the current Node server. +- Frontend build and browser shell evidence are captured. +- Native Rust/Tauri checks are blocked by missing local toolchain, so this checkpoint is implemented but native-unproven. + +### Task 2 Correctness Review + +Findings: + +- No runtime correctness issues found in the scaffold shell. +- Residual risk: Tauri native compilation must be run after installing Rust, rustup, and Visual Studio Build Tools with MSVC/Windows SDK. + +### Task 2 Maintainer Review + +Findings: + +- No duplicate Windows implementation path introduced. +- The placeholder `getShellSnapshot()` is intentionally local shell scaffolding; Task 6 must replace it with typed Tauri command calls. + +### Task 3 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 3 scope is limited to domain models, validation, TypeScript mirror types, and domain tests. +- Current Node gateway/client runtime files remain out of scope. + +### Task 3 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- Models and validation were added in the Tauri app slice only. +- TypeScript build passed. +- Rust domain tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing, so this checkpoint is implemented but Rust-unproven. + +### Task 3 Correctness Review + +Findings: + +- No TypeScript/runtime issues found by `npm run build`. +- Residual risk: Rust syntax and tests must be validated once Rust and MSVC Build Tools are installed. + +### Task 3 Maintainer Review + +Findings: + +- No duplicate truth path introduced. +- The Rust validation module is currently imported by integration tests via path modules because Task 3 did not modify `lib.rs`; Task 6 should expose stable crate modules when commands integrate the domain layer. + +### Task 4 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 4 scope is limited to JSON storage, activity helpers, and storage tests. +- UI screens, current Node gateway/client code, and adapter/helper behavior remain out of scope. + +### Task 4 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- Implementation stayed within the Tauri Rust storage/activity slice and storage tests. +- Source truth remains JSON under the planned ProgramData root, with generated artifacts represented only as a path directory. +- Rust tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing. + +### Task 4 Correctness Review + +Findings: + +- No TypeScript/runtime issues found by `npm run build`. +- Storage writes use temp files and create backups before overwriting existing source files. +- Invalid JSON fallback intentionally returns empty collections, matching the Task 4 evidence contract. +- Residual risk: Rust syntax and behavior must be validated once Rust and MSVC Build Tools are installed. + +### Task 4 Maintainer Review + +Findings: + +- No duplicate storage truth path introduced. +- Activity ordering/capping is isolated in `activity.rs` so future commands can reuse it without duplicating list policy. +- The Rust modules are currently imported by integration tests via path modules because Task 4 did not modify `lib.rs`; Task 6 should expose stable crate modules when commands integrate storage. + +### Task 5 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 5 scope is limited to proxy-router adapter boundary, ProxiFyre generated config, and adapter tests. +- UI screens, current Node gateway/client code, helper/service operations, and Tauri commands remain out of scope. + +### Task 5 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- Implementation stayed within the Tauri Rust adapter slice and ProxiFyre adapter tests. +- `ProxyRouterAdapter` keeps UI and source profile models separated from ProxiFyre-specific config fields. +- External SOCKS5 generation explicitly does not depend on Local sing-box component state. +- Rust tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing. + +### Task 5 Correctness Review + +Findings: + +- No TypeScript/runtime issues found by `npm run build`. +- ProxiFyre output uses the current official object-shaped `app-config.json` format with a `proxies` list. +- Component dependency checks are target-driven, so missing `singbox` blocks only targets that declare `requires_component: singbox`. +- Residual risk: Rust syntax and behavior must be validated once Rust and MSVC Build Tools are installed. + +### Task 5 Maintainer Review + +Findings: + +- No duplicate adapter path introduced. +- ProxiFyre-specific DTOs stay inside `proxifyre.rs`; shared callers should depend on `ProxyRouterAdapter` and `ProxyRouterGeneratedConfig`. +- The Rust adapter modules are currently imported by integration tests via path modules because Task 5 did not modify `lib.rs`; Task 6 should expose stable crate modules when commands integrate adapters. + +### Task 6 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 6 scope is limited to Tauri command handlers, command wrappers, command tests, and command registration. +- Full UI screens, privileged helper implementation, installer scripts, and current Node gateway/client code remain out of scope. + +### Task 6 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- Implementation stayed in the Tauri command/API slice and did not implement the Task 7 UI. +- Commands return structured DTOs and errors instead of raw PowerShell/stdout parsing. +- Apply remains behind adapter/helper interfaces; the Task 6 helper is staged/mock-style and does not perform hidden privileged installation. +- Rust command tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing. + +### Task 6 Correctness Review + +Findings: + +- No TypeScript/runtime issues found by `npm run build`. +- Apply writes a generated ProxiFyre config artifact, calls the helper boundary, and records success activity. +- Apply also records error activity when blocked by adapter checks, preserving user-visible failure evidence. +- Residual risk: Rust syntax and command macro wiring must be validated once Rust and MSVC Build Tools are installed. + +### Task 6 Maintainer Review + +Findings: + +- Command DTOs keep the TypeScript boundary camelCase without changing the source domain model storage contract. +- The command service functions are separately callable from tests, so future helper integration can replace `StagedApplyHelper` without rewriting the UI wrapper. +- Residual maintainability caveat: `src-tauri/src/lib.rs` still contains the original scaffold `run()` path without command registration because Task 6's allowed file list targeted `main.rs`; a later cleanup should align or remove that stale scaffold entrypoint. + +### Task 7 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings in the plan contract. +- Task 7 scope is limited to React app/features/styles and must not change Rust command/adapter behavior. +- Required evidence is app-visible state, but browser automation availability must be verified during the task. + +### Task 7 POST Review + +Mode: POST + +Verdict: aligned with browser evidence caveat. + +Findings: + +- No blocker or major findings in the implemented UI slice. +- Implementation stayed within `apps/windows-client/src/app`, `src/features`, and `src/styles`. +- UI is wired to Task 6 command wrappers with a preview fallback, so the desktop UI can be developed in a browser while still using Tauri commands in-app. +- Browser screenshot/DOM evidence could not be captured because the browser runtime failed with the sandbox helper error. + +### Task 7 Correctness Review + +Findings: + +- `npm run build` passed after the UI rewrite. +- Overview, Profiles, Targets, Components, and Logs now expose the MVP workflows requested by Task 7. +- Apply, profile preview, profile save, and target save call the command wrapper boundary and fall back locally when outside Tauri. +- Residual risk: actual browser rendering and Tauri runtime command calls must be verified once the sandbox/browser helper issue is resolved. + +### Task 7 Maintainer Review + +Findings: + +- UI state is centralized in `App.tsx` for the MVP; this is acceptable for Task 7 but should move to query/state hooks if the app grows. +- The preview fallback is isolated to UI state and does not create a second source of truth for persisted configuration. +- `apply_patch` was unavailable due sandbox helper failure, so Task 7 edits used escalated `Set-Content` fallback; future edits should return to `apply_patch` when the tool is healthy. + +### Task 8 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 8 scope is limited to helper abstraction, installer scripts, capability declaration, and helper tests. +- Hidden installer invocation inside profile apply remains forbidden and was not added. + +### Task 8 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- Helper boundary returns structured JSON and rejects unstructured stdout. +- Installer entrypoints are explicit per component and expose `-PlanOnly` evidence without admin/install side effects. +- Capability remains narrow; no broad shell/sidecar permission was introduced. +- Rust helper tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing. + +### Task 8 Correctness Review + +Findings: + +- `npm run build` passed after Task 8 changes. +- PowerShell parser checks passed for all three installer scripts. +- `-PlanOnly` output proves all three installer scripts return machine-readable JSON and do not perform install work in plan mode. +- Residual risk: real elevated install/service/apply behavior still requires manual Windows verification with installed helper and components. + +### Task 8 Maintainer Review + +Findings: + +- No duplicate helper path introduced; `helper.rs` owns the helper JSON contract and runner abstraction. +- Installer scripts remain thin boundaries and do not contain application profile/apply logic. +- Task 6 still uses `StagedApplyHelper`; integrating the real helper into apply should be done in a later command/helper integration task or package verification pass. + +### Task 9 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings. +- Task 9 scope is limited to the optional sing-box adapter, adapter tests, and Targets/Components UI surfaces. +- The plan forbids making sing-box mandatory for external targets; implementation must keep that dependency attached only to local targets declaring `requires_component: singbox`. + +### Task 9 POST Review + +Mode: POST + +Verdict: aligned with environment caveat. + +Findings: + +- No blocker or major findings. +- `SingBoxAdapter` is separate from `ProxiFyreAdapter` and does not alter external target apply behavior. +- Binary validation is explicit and optional: config generation calls a checker only when a sing-box binary path is supplied. +- UI changes make Local sing-box an explicit install prompt when absent instead of presenting it as a normal ready external path. +- Rust tests are authored but not executable in this environment because Rust/Cargo/MSVC are missing. + +### Task 9 Correctness Review + +Findings: + +- `npm run build` passed after Task 9 UI changes. +- Local sing-box config generation checks the local target shape and requires the sing-box component to be installed and running. +- The Task 9 tests include external ProxiFyre generation with missing sing-box to preserve the no-mandatory-sing-box invariant. +- Residual risk: Rust syntax and actual `sing-box check` execution must be validated once Rust, MSVC Build Tools, and a real sing-box binary are installed. + +### Task 9 Maintainer Review + +Findings: + +- No hidden installer path was introduced; install/service flows remain behind the explicit helper and installer boundary from Task 8. +- sing-box generated config remains a derived artifact; no new source-of-truth JSON path was added. +- The new adapter is currently test-imported directly like earlier Rust integration tests; a later integration cleanup should expose adapter modules through the stable crate entrypoint when package verification wires Local sing-box into commands. + +### Task 10 PRE Review + +Mode: PRE + +Verdict: aligned. + +Findings: + +- No blocker findings in the plan contract. +- Task 10 scope is documentation, final verification, target-perspective evidence, and the Windows app slice. +- The acceptance gate requires honest handling of native Windows/Rust/service checks that cannot run in this environment. + +### Task 10 POST Review + +Mode: POST + +Verdict: aligned with native verification blocker. + +Findings: + +- No blocker or major findings in the Task 10 documentation and verification updates. +- README and roadmap now describe the separate Control App, Proxyfier Layer, and optional Local sing-box flows. +- Browser evidence proves the MVP app surfaces and external SOCKS5 route line from the user's perspective. +- Installer `-PlanOnly` evidence proves explicit component boundaries without side effects. +- Native Tauri build, Rust tests, and real elevated service/helper behavior remain implemented but unproven until Rust/Cargo/MSVC and component binaries/services are available. + +### Task 10 Correctness Review + +Findings: + +- `npm run build` passed after documentation updates. +- Browser preview apply showed the intended generated ProxiFyre config path and staged helper response. +- `cargo test` and `npm run tauri -- build` are blocked by missing `cargo`; this is recorded as an environment blocker rather than a passing result. +- Real generated ProxiFyre file creation through Tauri commands is not proven in this environment because native command execution cannot run without the Rust toolchain. + +### Task 10 Maintainer Review + +Findings: + +- No current Node gateway/client runtime files were changed for Windows behavior. +- The Windows client README keeps operational knowledge local to `apps/windows-client`, while the root README points to it. +- No additional source-of-truth path was introduced; docs keep JSON under `C:\ProgramData\VpnProxy\config` as source and generated files under `generated` as derived artifacts. +- Residual cleanup for a future task: expose Rust modules through a stable crate entrypoint and run full native tests/build once the Windows toolchain is installed. + + diff --git a/docs/goals/windows-modular-client/GOAL.md b/docs/goals/windows-modular-client/GOAL.md new file mode 100644 index 0000000..e9455e8 --- /dev/null +++ b/docs/goals/windows-modular-client/GOAL.md @@ -0,0 +1,16 @@ +# Goal: Windows Tauri Proxy Client + +Use Krypton Execution to execute `docs/goals/windows-modular-client/PLAN.md`. + +Core rules: +- Treat `PLAN.md` as the source plan. +- Preserve intent, ownership, contract, cutover, evidence, and kill criteria. +- Build a separate Tauri 2 Windows desktop app under `apps/windows-client`. +- Do not implement Windows by extending the current Node gateway/client server. +- Keep Control App, Proxyfier Layer, and Local sing-box separately installable and operable. +- Make external proxy target + ProxiFyre profile apply the MVP. +- Keep Local sing-box optional; it must not be required for external target profiles. +- Keep generated ProxiFyre and sing-box configs derived from source models. +- Capture acceptance evidence from the target user's perspective and record it in `EVIDENCE.md`. +- Say "implemented but unproven" if Windows-only privileged evidence cannot be captured. + diff --git a/docs/goals/windows-modular-client/PLAN.md b/docs/goals/windows-modular-client/PLAN.md new file mode 100644 index 0000000..33af55e --- /dev/null +++ b/docs/goals/windows-modular-client/PLAN.md @@ -0,0 +1,506 @@ +# Windows Tauri Proxy Client Implementation Plan + +**Intent:** Build a separate Windows desktop proxy management app using Tauri 2, React, TypeScript, and Rust. The app manages three independent components: Control App, Proxyfier Layer, and optional Local sing-box. +**Current Behavior:** The repo contains a gateway/client Node + React web application and planning documents for a Windows mode inside that app. A newer product/technology brief now targets a standalone Windows desktop utility instead of extending the existing web control panel. +**Expected Outcome:** A compact Windows desktop utility lets the user configure app-level proxy routing through external SOCKS5/HTTP targets first, then optionally install and use local sing-box. The app remains useful when sing-box is absent. +**Target-Perspective Output:** A Windows user opens the desktop app, sees Overview, Profiles, Targets, Components, and Logs, adds Discord or another process/folder/exe profile, selects an external proxy target, applies changes to the proxyfier layer, and sees component status plus recent activity. Later, installing Local sing-box adds a local target without changing the profile model. +**Truth Owner:** Source configuration lives in the Tauri app's Rust domain model and JSON files under `C:\ProgramData\VpnProxy`. Generated ProxiFyre and sing-box configs are derived artifacts. Privileged install/service operations are owned by explicit helper/installer flows, not by React UI state. +**Contract Boundary:** React UI calls typed Tauri commands. Tauri Rust backend validates and persists profiles/targets/components. Proxy routing is behind a `ProxyRouterAdapter` boundary, with ProxiFyre as the first adapter. Privileged operations go through explicit helper/install commands returning structured JSON. +**Cutover:** Supersede the prior Node `APP_MODE=windows` implementation direction. Keep existing gateway/client code intact. New Windows work lives under a separate Tauri app slice. +**Displaced Path:** The old plan to add Windows mode into `src/server`/`src/web` is demoted to historical context. Do not add a third app mode to the current Node server for this product. +**Value Density:** The smallest high-value slice is the desktop app MVP with external SOCKS5 target + ProxiFyre profile apply. Local sing-box is optional and comes after the proxyfier MVP is proven. +**Evidence Gate:** Evidence must include target-perspective app proof: built Tauri app or dev window screenshot/state, generated proxyfier config artifact, mocked or real helper response, and manual Windows checklist when privileged components are involved. +**Acceptance Evidence:** Automated tests pass for Rust/TypeScript domain logic, app build succeeds, the MVP can create a profile and generate/apply ProxiFyre config against an external target, and Windows manual evidence proves independent component behavior. +**Evidence Lane:** Record command output, app screenshots/state payloads, generated configs, and manual verification in `docs/goals/windows-modular-client/EVIDENCE.md`. +**Kill Criteria:** No Windows implementation inside current Node gateway/client server; no mandatory sing-box dependency; no generated config as source truth; no hidden installation during profile apply; no direct UI parsing of raw PowerShell/stdout. +**Architecture Slice:** New standalone Tauri app under `apps/windows-client`, plus docs updates that point from older Windows plans to this plan. +**Plan Review Gate:** Requires PRE review before implementation execution. + +## Source Brief + +Product and technology source brief: + +- `docs/windows-client-product-tech-brief.md` + +This plan turns that brief into an execution-ready implementation sequence. + +## Outcome Contract + +Plan title: Windows Tauri Proxy Client + +Intent: Build a native-feeling Windows utility that manages app-level proxy routing while keeping Control App, Proxyfier Layer, and Local sing-box separately installable and operable. + +Current behavior: +- Existing runtime code is a Node HTTP server and Vite/React web UI for gateway and Mac-style client modes. +- Earlier Windows docs describe adding Windows mode to that existing app. +- The selected direction is now Tauri 2 + React/TypeScript + Rust as a separate Windows desktop app. + +Expected outcome: +- `apps/windows-client` contains a Tauri 2 app. +- The app has Overview, Profiles, Targets, Components, and Logs surfaces. +- Profiles store process/folder/exe source items. +- Targets store external proxy endpoints and optional local sing-box. +- ProxiFyre is the first proxy router adapter. +- Local sing-box is optional and never required for external target profiles. + +Target-perspective output: +- User can install/run only the Control App. +- User can see Proxyfier and Local sing-box as separate components. +- User can add an external SOCKS5 target. +- User can add a Discord process profile. +- User can apply the profile to generated ProxiFyre config. +- User sees activity confirming whether apply succeeded or why it was blocked. + +Truth owner: +- Rust core domain crate owns normalized models and validation. +- JSON source files under `C:\ProgramData\VpnProxy\config` own persisted profiles/targets/component preferences. +- `ProxyRouterAdapter` owns conversion from source models to proxy-router generated config. +- `SingBoxAdapter` owns generated local sing-box config and service contract. +- React UI owns only transient UI state. + +Contract boundary: +- UI -> Tauri commands with typed request/response DTOs. +- Tauri commands -> Rust core services. +- Core services -> adapter traits. +- Adapter traits -> helper/install/service commands when privileged operations are needed. +- Helper/install commands return structured JSON, never unstructured text for app logic. + +Cutover: +- Add superseded notes to old Windows Node-mode docs. +- Keep `docs/windows-client-product-tech-brief.md` as product brief. +- Make this `PLAN.md` the execution plan. +- Do not implement Windows by adding `APP_MODE=windows` to the current Node server. + +Displaced path: +- Displace old "Windows mode in current web app" implementation. +- Displace "full install vs ProxiFyre-only" as dominant architecture; those become recipes composed from separate components. + +Value density: +- MVP must prove app-level routing with external proxy target and ProxiFyre before local sing-box work expands scope. + +Evidence gate: +- Tests and build are not enough. +- Capture app-visible state and generated config. +- Capture Windows manual evidence for service/helper actions when those tasks execute. + +Acceptance evidence: +- `cargo test` or equivalent Rust tests for domain/adapters. +- frontend typecheck/test/build for React. +- Tauri dev/build command result. +- Screenshot or state dump showing Windows app surfaces. +- Generated ProxiFyre config from a sample profile. +- Manual Windows checklist when privileged components are present. + +Non-goals: +- No Electron. +- No extension of the current Node gateway/client UI for Windows MVP. +- No global Windows system proxy changes. +- No transparent routing without a proxy router. +- No mandatory local sing-box. +- No direct coupling of UI to ProxiFyre-specific config shape. + +Risk if wrong: +- If built inside the current Node app, the product will inherit gateway/client assumptions and conflict with the selected Tauri direction. +- If ProxiFyre is not behind an adapter, licensing or engine changes will force UI/data rewrites. +- If privileged work is hidden behind apply, users lose control and failures become hard to diagnose. + +## Architecture Slice + +Files/directories to create: +- `apps/windows-client/package.json` +- `apps/windows-client/vite.config.ts` +- `apps/windows-client/tsconfig.json` +- `apps/windows-client/src/main.tsx` +- `apps/windows-client/src/app/App.tsx` +- `apps/windows-client/src/app/routes.tsx` +- `apps/windows-client/src/api/tauriCommands.ts` +- `apps/windows-client/src/domain/types.ts` +- `apps/windows-client/src/features/overview/*` +- `apps/windows-client/src/features/profiles/*` +- `apps/windows-client/src/features/targets/*` +- `apps/windows-client/src/features/components/*` +- `apps/windows-client/src/features/logs/*` +- `apps/windows-client/src/styles/*` +- `apps/windows-client/src-tauri/Cargo.toml` +- `apps/windows-client/src-tauri/tauri.conf.json` +- `apps/windows-client/src-tauri/capabilities/default.json` +- `apps/windows-client/src-tauri/src/main.rs` +- `apps/windows-client/src-tauri/src/commands.rs` +- `apps/windows-client/src-tauri/src/models.rs` +- `apps/windows-client/src-tauri/src/storage.rs` +- `apps/windows-client/src-tauri/src/activity.rs` +- `apps/windows-client/src-tauri/src/adapters/proxy_router.rs` +- `apps/windows-client/src-tauri/src/adapters/proxifyre.rs` +- `apps/windows-client/src-tauri/src/adapters/singbox.rs` +- `apps/windows-client/src-tauri/src/helper.rs` +- `apps/windows-client/src-tauri/tests/*` +- `apps/windows-client/scripts/install-control-app.ps1` +- `apps/windows-client/scripts/install-proxyfier.ps1` +- `apps/windows-client/scripts/install-singbox.ps1` + +Files to modify: +- `README.md` +- `docs/roadmap.md` +- `docs/superpowers/specs/2026-05-21-windows-client-design.md` +- `docs/superpowers/plans/2026-05-21-windows-client.md` +- `docs/goals/windows-modular-client/GOAL.md` +- `docs/goals/windows-modular-client/EVIDENCE.md` + +Files to avoid: +- `src/server/*` except if a later explicit migration asks for shared code extraction. +- `src/web/*` for Windows MVP. +- Docker, entrypoint, and compose files. +- macOS installer. + +Source of truth: +- `C:\ProgramData\VpnProxy\config\profiles.json` +- `C:\ProgramData\VpnProxy\config\targets.json` +- `C:\ProgramData\VpnProxy\config\components.json` +- `C:\ProgramData\VpnProxy\state\activity.json` + +Derived artifacts: +- `C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json` +- `C:\ProgramData\VpnProxy\generated\sing-box-config.json` +- ProxiFyre runtime config copied/backed up by helper/apply operation. + +Read path: +- React UI calls Tauri commands. +- Tauri commands read JSON source via Rust storage service. +- Component status combines source preferences, filesystem checks, service checks, and helper responses. + +Write path: +- React UI sends typed mutations. +- Rust validates with domain models. +- Rust writes source JSON atomically with backups. +- Apply generates derived config and invokes adapter/helper. + +Integration points: +- ProxiFyre adapter emits `app-config.json` compatible output. +- Local sing-box adapter emits `sing-box` JSON config and validates via `sing-box check` when binary exists. +- Tauri sidecar/helper permissions are declared explicitly. +- Installer scripts may be launched or displayed explicitly, never silently during apply. + +Migration/cutover: +- Older Windows docs point to this plan and source brief. +- Existing Node app remains gateway/client only. +- If shared subscription parsing is needed later, extract it intentionally into a shared package rather than importing server internals. + +Acceptance evidence gate: +- MVP evidence must show external-target flow works without local sing-box. +- Optional sing-box evidence must show the same profile model can switch targets after installing sing-box. + +## Task Board + +### Task 1: Supersede Old Windows Node Plan + +Owner: main agent + +Input: +- `docs/windows-client-product-tech-brief.md` +- old Windows docs/plans + +Files allowed: +- `docs/superpowers/specs/2026-05-21-windows-client-design.md` +- `docs/superpowers/plans/2026-05-21-windows-client.md` +- `docs/roadmap.md` +- `README.md` + +Files forbidden: +- Runtime source files. + +Output: +- Old Windows documents clearly point to this Tauri plan and no longer read as implementation authority. + +Evidence: +- `rg -n "Tauri|superseded|windows-client-product-tech-brief|apps/windows-client" README.md docs` + +Depends on: none + +Parallel safe: yes + +### Task 2: Scaffold Tauri App Shell + +Owner: main agent + +Input: +- Tauri 2 app structure +- Product brief UI surfaces + +Files allowed: +- `apps/windows-client/package.json` +- `apps/windows-client/vite.config.ts` +- `apps/windows-client/tsconfig.json` +- `apps/windows-client/index.html` +- `apps/windows-client/src/*` +- `apps/windows-client/src-tauri/*` + +Files forbidden: +- Current root `src/server/*` +- Current root `src/web/*` + +Output: +- Tauri app starts with empty shell and five navigation surfaces. +- No business logic yet. + +Evidence: +- `cd apps/windows-client && npm install && npm run build` +- `cd apps/windows-client/src-tauri && cargo test` if Rust tests exist + +Depends on: Task 1 + +Parallel safe: no + +### Task 3: Define Domain Models And Validation + +Owner: main agent + +Input: +- Profile/Target/Component models from brief + +Files allowed: +- `apps/windows-client/src-tauri/src/models.rs` +- `apps/windows-client/src-tauri/src/validation.rs` +- `apps/windows-client/src/domain/types.ts` +- `apps/windows-client/src-tauri/tests/domain_tests.rs` + +Files forbidden: +- Adapter/helper code except trait references. + +Output: +- Typed Rust models for `Profile`, `ProfileItem`, `Target`, `ComponentStatus`, `ActivityEntry`. +- TypeScript DTOs mirror Rust command responses. +- Validation rejects malformed ports/protocols but allows missing local sing-box. + +Evidence: +- Rust tests showing process/folder/exe normalization and external target validation. + +Depends on: Task 2 + +Parallel safe: no + +### Task 4: Implement JSON Storage And Activity Log + +Owner: main agent + +Input: +- Domain models from Task 3 + +Files allowed: +- `apps/windows-client/src-tauri/src/storage.rs` +- `apps/windows-client/src-tauri/src/activity.rs` +- `apps/windows-client/src-tauri/tests/storage_tests.rs` + +Files forbidden: +- UI screens except command wiring stubs. + +Output: +- Atomic JSON read/write for profiles, targets, components, and activity. +- Backups before overwriting source files. +- Config root defaults to `C:\ProgramData\VpnProxy`, with test override. + +Evidence: +- Tests prove roundtrip, invalid JSON fallback behavior, backup creation, activity cap/sort. + +Depends on: Task 3 + +Parallel safe: no + +### Task 5: Add Proxy Router Adapter Boundary And ProxiFyre Adapter + +Owner: main agent + +Input: +- Domain models and storage + +Files allowed: +- `apps/windows-client/src-tauri/src/adapters/proxy_router.rs` +- `apps/windows-client/src-tauri/src/adapters/proxifyre.rs` +- `apps/windows-client/src-tauri/tests/proxifyre_adapter_tests.rs` + +Files forbidden: +- Direct UI coupling to ProxiFyre config fields. + +Output: +- `ProxyRouterAdapter` trait. +- `ProxiFyreAdapter` generates config from enabled profiles and targets. +- External target flow does not require sing-box. + +Evidence: +- Test generates ProxiFyre config for Discord + external SOCKS5 target. +- Test blocks local-singbox target only when target requires missing component. + +Depends on: Task 4 + +Parallel safe: no + +### Task 6: Add Tauri Commands + +Owner: main agent + +Input: +- Storage and adapter services + +Files allowed: +- `apps/windows-client/src-tauri/src/commands.rs` +- `apps/windows-client/src-tauri/src/main.rs` +- `apps/windows-client/src/api/tauriCommands.ts` +- `apps/windows-client/src-tauri/tests/command_tests.rs` + +Files forbidden: +- Full UI implementation beyond command call wrappers. + +Output: +- Commands for status, profiles, targets, components, scan/resolve preview, apply, logs. +- Commands return structured responses only. + +Evidence: +- Command tests or integration tests prove apply generates derived config and records activity using a mock adapter/helper. + +Depends on: Task 5 + +Parallel safe: no + +### Task 7: Build MVP UI + +Owner: main agent + +Input: +- Tauri command API +- Product brief layout + +Files allowed: +- `apps/windows-client/src/app/*` +- `apps/windows-client/src/features/overview/*` +- `apps/windows-client/src/features/profiles/*` +- `apps/windows-client/src/features/targets/*` +- `apps/windows-client/src/features/components/*` +- `apps/windows-client/src/features/logs/*` +- `apps/windows-client/src/styles/*` + +Files forbidden: +- Rust adapter behavior except fixing DTO mismatches. + +Output: +- Compact utility UI with Overview, Profiles, Targets, Components, Logs. +- User can create/edit profile, external target, and trigger apply. +- Missing sing-box is shown as valid optional state. + +Evidence: +- `npm run build` +- Screenshot or browser/app state showing missing sing-box and usable external target flow. + +Depends on: Task 6 + +Parallel safe: no + +### Task 8: Implement Helper And Explicit Installer Boundary + +Owner: main agent + +Input: +- Component model +- Security model from brief + +Files allowed: +- `apps/windows-client/src-tauri/src/helper.rs` +- `apps/windows-client/src-tauri/capabilities/default.json` +- `apps/windows-client/scripts/install-control-app.ps1` +- `apps/windows-client/scripts/install-proxyfier.ps1` +- `apps/windows-client/scripts/install-singbox.ps1` +- `apps/windows-client/src-tauri/tests/helper_tests.rs` + +Files forbidden: +- Hidden installer invocation inside profile apply. + +Output: +- Helper command abstraction for status/service/apply. +- Installer scripts are explicit and idempotent. +- Tauri sidecar/shell permissions are narrow and documented. + +Evidence: +- Helper tests with mock command runner. +- PowerShell parser checks for installer scripts. +- Capability file shows limited sidecar permissions. + +Depends on: Task 6 + +Parallel safe: partly, after command DTOs are stable + +### Task 9: Add Optional Local Sing-Box Adapter + +Owner: main agent + +Input: +- sing-box target model +- service/helper boundary + +Files allowed: +- `apps/windows-client/src-tauri/src/adapters/singbox.rs` +- `apps/windows-client/src-tauri/tests/singbox_adapter_tests.rs` +- `apps/windows-client/src/features/components/*` +- `apps/windows-client/src/features/targets/*` + +Files forbidden: +- Making sing-box mandatory for external targets. + +Output: +- Generate local sing-box config. +- Validate via `sing-box check` when binary exists. +- Local target appears only when installed/configured or as an explicit install prompt. + +Evidence: +- Tests show external target apply works without sing-box. +- Tests show local-singbox target requires installed/running component. + +Depends on: Tasks 5 and 8 + +Parallel safe: no + +### Task 10: Package, Verify, And Record Evidence + +Owner: main agent + +Input: +- Completed MVP implementation + +Files allowed: +- `apps/windows-client/*` +- `README.md` +- `docs/roadmap.md` +- `docs/goals/windows-modular-client/EVIDENCE.md` + +Files forbidden: +- Unrelated app code. + +Output: +- Build/test commands documented. +- README explains separate Control App, Proxyfier, and Local sing-box install flows. +- Evidence file captures automated and target-perspective proof. + +Evidence: +- `npm run build` +- Rust tests +- Tauri build/dev proof +- generated ProxiFyre config summary +- UI screenshot/state +- Windows manual checklist, or clearly mark `implemented but unproven` for Windows-only service behavior if not run on a Windows host. + +Depends on: all previous tasks + +Parallel safe: no + +## Manual Windows Verification Checklist + +1. Install/run only Control App. +2. Verify Proxyfier and Local sing-box show missing as separate components. +3. Add external SOCKS5 target. +4. Add Discord process profile. +5. Apply profile; verify generated ProxiFyre config and activity entry. +6. Install Proxyfier separately; verify status changes. +7. Apply profile to real Proxyfier service. +8. Install Local sing-box separately. +9. Import subscription or config, select outbound, and start Local sing-box. +10. Switch existing profile from external target to Local sing-box and apply. +11. Stop/restart Proxyfier and Local sing-box separately. +12. Copy diagnostics and verify secrets are redacted. + diff --git a/docs/roadmap.md b/docs/roadmap.md index a272789..5cb9477 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -8,7 +8,7 @@ | --- | --- | --- | --- | | `gateway` | LXC/VPS как gateway для роутера и всей сети | Docker `network_mode: host` + TProxy | делаем первым | | `desktop-proxy` | Mac/Linux локальный HTTP/SOCKS proxy с fallback | Docker bridged ports | позже переносим из старой реализации | -| `windows-gaming` | Windows для игр/Discord/Vesktop | native `sing-box.exe` + ProxiFyre | позже приводим в порядок | +| `windows-gaming` | Windows для игр/Discord/Vesktop | standalone Tauri 2 app + ProxiFyre adapter + optional native `sing-box.exe` | активное направление: `docs/goals/windows-modular-client/PLAN.md` | ## Gateway mode @@ -84,15 +84,32 @@ ## Windows gaming mode -Цель: сохранить сценарий для Discord/Vesktop/игр. +Цель: отдельное Windows desktop-приложение для Discord/Vesktop/игр, где Control App, Proxyfier Layer и Local sing-box являются независимыми компонентами. + +Current checkpoint: + +- MVP slice exists under `apps/windows-client`. +- Frontend build passes with `npm run build`. +- Rust/Tauri native verification requires installing Rust/rustup and Visual Studio Build Tools with MSVC/Windows SDK. +- Local sing-box is optional; external SOCKS5 targets remain the first verified path. Требования: -- Native `sing-box.exe`. -- Scheduled task или Windows service. -- ProxiFyre + WinPacketFilter для приложений, которые не умеют proxy. -- Управление из PowerShell helper. -- Позже можно сделать Electron/Tauri UI поверх privileged helper. +- Standalone Tauri 2 + React/TypeScript + Rust app under `apps/windows-client`. +- Profiles for process/folder/exe app routing. +- External SOCKS5/HTTP targets first; local `sing-box` is optional. +- Proxyfier adapter boundary with ProxiFyre as the first engine. +- Explicit installers for Control App, Proxyfier Layer, and Local sing-box. +- Privileged helper/install operations return structured JSON. + +Source docs: + +- Product/tech brief: `docs/windows-client-product-tech-brief.md`. +- Execution plan: `docs/goals/windows-modular-client/PLAN.md`. + +Superseded: + +- The old Node `APP_MODE=windows` plan in `docs/superpowers/plans/2026-05-21-windows-client.md` is historical context, not the active implementation path. ## Рабочий порядок @@ -102,4 +119,4 @@ 4. Реализовать Vite + React UI для subscription -> server select -> apply. 5. Добавить gateway docs/install script. 6. Потом переносить desktop-proxy. -7. Потом приводить Windows mode к новой архитектуре. +7. Потом реализовать standalone Windows Tauri client по `docs/goals/windows-modular-client/PLAN.md`. diff --git a/docs/superpowers/plans/2026-05-21-windows-client.md b/docs/superpowers/plans/2026-05-21-windows-client.md index 9ca0886..aeb66e0 100644 --- a/docs/superpowers/plans/2026-05-21-windows-client.md +++ b/docs/superpowers/plans/2026-05-21-windows-client.md @@ -1,5 +1,12 @@ # Windows Client Implementation Plan +> Superseded: do not execute this Node `APP_MODE=windows` plan as the current +> Windows implementation path. The active plan is the standalone Tauri 2 desktop +> app in `docs/goals/windows-modular-client/PLAN.md`, based on +> `docs/windows-client-product-tech-brief.md`. +> Content below is retained for historical context and may contradict the active +> Tauri plan. + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Restore the Windows proxy workflow as a script-first product with two install modes: full local `sing-box` + ProxiFyre, or ProxiFyre-only routing to an existing proxy, controlled by a clean local web UI. diff --git a/docs/superpowers/specs/2026-05-21-windows-client-design.md b/docs/superpowers/specs/2026-05-21-windows-client-design.md index ee0e41f..6f2e851 100644 --- a/docs/superpowers/specs/2026-05-21-windows-client-design.md +++ b/docs/superpowers/specs/2026-05-21-windows-client-design.md @@ -1,5 +1,12 @@ # Windows Client Design +> Superseded: this document describes the earlier Node/web-control Windows direction. +> The active Windows direction is a standalone Tauri 2 desktop app under +> `apps/windows-client`, driven by `docs/windows-client-product-tech-brief.md` +> and `docs/goals/windows-modular-client/PLAN.md`. +> Content below is retained for historical context and may contradict the active +> Tauri plan. + ## Goal Restore the old Windows workflow in a cleaner product shape: a one-command PowerShell installer can install either a full local `sing-box` + ProxiFyre setup or ProxiFyre-only routing to an existing proxy, then expose a small local web UI for profiles, folders, executable files, status, and logs. diff --git a/docs/windows-client-product-tech-brief.md b/docs/windows-client-product-tech-brief.md new file mode 100644 index 0000000..0161ae4 --- /dev/null +++ b/docs/windows-client-product-tech-brief.md @@ -0,0 +1,635 @@ +# Windows Proxy Client: Product And Technology Brief + +Дата: 2026-07-03 + +Цель документа: описать, как должно выглядеть и работать Windows-приложение для управления proxy/VPN-маршрутизацией приложений, и какой стек лучше использовать для реализации. + +Этот документ можно отдать другой модели или команде как исходное ТЗ. + +## Коротко + +Нужно Windows-приложение, которое разделяет систему на три независимые части: + +1. **Control App**: маленькое desktop-приложение для настройки, статуса, профилей, логов и запуска операций. +2. **Proxyfier Layer**: отдельный компонент, который заставляет выбранные Windows-приложения ходить через SOCKS5/HTTP proxy, даже если они сами не умеют proxy. +3. **Local sing-box**: опциональный локальный VPN/proxy runtime. Его можно установить, не устанавливать, остановить, заменить внешним proxy target. + +Главный принцип: пользователь не обязан ставить все сразу. Если у него уже есть proxy, ему нужны только Control App + Proxyfier. Если нужен локальный VPN-клиент, он отдельно ставит `sing-box`. + +## Как это должно выглядеть + +Приложение должно выглядеть как компактная системная утилита, а не как сайт. + +Главный экран: + +- верхняя строка: общий статус маршрута; +- три карточки компонентов: `Control App`, `Proxyfier`, `Local sing-box`; +- список активных профилей; +- кнопка `Apply changes`; +- короткая лента последних событий. + +Пример главного статуса: + +```text +Selected apps -> ProxiFyre -> Local sing-box 127.0.0.1:1080 -> VPN +``` + +или: + +```text +Selected apps -> ProxiFyre -> Existing proxy 192.168.50.111:8080 +``` + +Если `sing-box` не установлен, это не ошибка. Карточка должна показывать: + +```text +Local sing-box +Not installed +Install if you want this PC to run its own local VPN proxy. +``` + +Если Proxyfier не установлен, профили можно редактировать, но apply должен быть заблокирован: + +```text +Proxyfier is required to route selected apps. +Install Proxyfier +``` + +## Основные экраны + +### 1. Overview + +Показывает: + +- текущий route line; +- статус Control App; +- статус Proxyfier; +- статус Local sing-box; +- активный proxy target; +- сколько приложений сейчас включено в routing; +- последние 5-10 событий. + +Действия: + +- restart Proxyfier; +- restart local sing-box, если установлен; +- open logs; +- copy diagnostics. + +### 2. Profiles + +Профиль - главный объект настройки. + +Профиль содержит: + +- название; +- enabled/disabled; +- proxy target; +- протоколы: TCP, UDP; +- список приложений. + +Типы элементов: + +- `process`: имя процесса, например `Discord`, `Telegram`, `Code`; +- `folder`: папка, приложение сканирует `.exe` внутри; +- `exe`: конкретный путь к `.exe`. + +UI профиля: + +- слева список профилей; +- справа детали выбранного профиля; +- поле выбора target; +- кнопки добавления: `Process`, `Folder`, `EXE`; +- preview resolved apps; +- `Save`; +- `Apply changes`. + +Важно: пользователь должен видеть понятные исходные элементы, а не только сгенерированный конфиг Proxyfier. + +### 3. Targets + +Proxy target - это куда Proxyfier отправляет трафик выбранных приложений. + +Типы targets: + +- `Local sing-box`: `127.0.0.1:1080`, доступен только если local sing-box установлен и запущен; +- `Existing SOCKS5 proxy`: например `127.0.0.1:8080` или `192.168.50.111:8080`; +- `Existing HTTP proxy`, если выбранный proxyfier поддерживает HTTP. + +На экране targets: + +- список targets; +- проверка соединения; +- имя, host, port, protocol; +- статус last checked; +- кнопка set default. + +### 4. Components + +Отдельный экран или часть Overview. + +Компоненты: + +- Control App; +- Proxyfier; +- Local sing-box. + +Для каждого: + +- installed / not installed; +- running / stopped; +- version; +- path; +- service/task status; +- actions. + +Actions должны быть явными: + +- `Install`; +- `Repair`; +- `Start`; +- `Stop`; +- `Restart`; +- `Open folder`; +- `View logs`. + +Нельзя делать скрытую установку `sing-box` при сохранении профиля. + +### 5. Logs / Diagnostics + +Должно быть две зоны: + +- activity: действия пользователя и результат apply; +- runtime logs: proxyfier logs, sing-box logs, helper logs. + +Кнопка `Copy diagnostics` должна собирать: + +- версии компонентов; +- paths; +- running status; +- активные profiles; +- targets без секретов; +- последние ошибки; +- путь к сгенерированному proxyfier config. + +## Пользовательские сценарии + +### Сценарий A: у пользователя уже есть proxy + +1. Пользователь устанавливает Control App. +2. Открывает приложение. +3. Видит, что Proxyfier не установлен, а sing-box отсутствует. +4. Нажимает `Install Proxyfier`. +5. Добавляет target `192.168.50.111:8080`. +6. Создает профиль `Discord`. +7. Добавляет process `Discord`. +8. Нажимает `Apply changes`. +9. Приложение генерирует config для Proxyfier и перезапускает proxyfier service. + +Результат: Discord ходит через внешний proxy. Local sing-box не нужен. + +### Сценарий B: пользователь хочет локальный VPN proxy + +1. Пользователь устанавливает Control App. +2. Устанавливает Proxyfier. +3. Устанавливает Local sing-box. +4. Вводит subscription/VLESS link. +5. Выбирает сервер. +6. Local sing-box поднимает SOCKS5/HTTP endpoint на `127.0.0.1:1080`. +7. Профили используют target `Local sing-box`. + +Результат: выбранные приложения ходят через локальный sing-box. + +### Сценарий C: временно отключить VPN + +1. Пользователь открывает профиль. +2. Меняет target с `Local sing-box` на внешний proxy или `Direct/Disabled`. +3. Нажимает `Apply changes`. + +Результат: Proxyfier перегенерирован, local sing-box можно остановить отдельно. + +## Рекомендуемый стек + +### Desktop shell: Tauri 2 + +Рекомендация: **Tauri 2 + React + TypeScript + Rust backend**. + +Почему: + +- Tauri ориентирован на маленькие desktop-приложения и использует системный web renderer, поэтому приложение легче Electron. +- Можно писать UI на обычном web stack: React/TypeScript/Vite. +- Backend-часть на Rust хорошо подходит для Windows APIs, файлов, процессов, sidecar binaries и безопасных команд. +- Tauri поддерживает sidecar binaries, но требует явно выдать permissions на запуск sidecar, что полезно для security boundary. + +Frontend: + +- React; +- TypeScript; +- Vite; +- TanStack Query для загрузки/кэша status/API; +- Zustand или Jotai для локального UI state; +- Zod для валидации JSON-моделей; +- CSS modules или Tailwind. Для этой утилиты лучше сдержанный Windows-like UI, без тяжелой дизайн-системы. + +Backend внутри Tauri: + +- Rust commands для простых операций; +- отдельный `core` crate с доменной логикой; +- отдельный `windows-helper` binary для elevated/privileged действий. + +Не рекомендую начинать с Electron, если нет жесткой причины. Electron проще для web-команды, но тяжелее по размеру и памяти. Для маленькой системной утилиты Tauri подходит лучше. + +### Privileged helper + +Нужно отделить обычное приложение от операций администратора. + +Рекомендуемая модель: + +```text +Tauri UI + -> Rust app backend + -> unprivileged status/read operations + -> explicit elevated helper for install/repair/service operations +``` + +Privileged helper может быть: + +- Rust CLI, который запускается elevated только для конкретной операции; +- Rust Windows service/helper, если нужен постоянный privileged agent; +- PowerShell scripts только как thin installer layer, не как основная бизнес-логика. + +Для MVP можно сделать проще: + +- installers запускаются отдельно от имени администратора; +- Control App работает обычным пользователем; +- service start/stop/restart идет через helper command; +- helper возвращает JSON, UI не парсит текст PowerShell. + +Контракт helper: + +```json +{ + "action": "proxyfier.apply", + "payload": { + "configPath": "C:\\Tools\\ProxiFyre\\app-config.json", + "config": {} + } +} +``` + +Ответ: + +```json +{ + "success": true, + "action": "proxyfier.apply", + "changed": true, + "message": "Proxyfier config applied and service restarted" +} +``` + +Ошибки: + +```json +{ + "success": false, + "action": "proxyfier.apply", + "error": "Proxyfier service is not installed", + "details": {} +} +``` + +### Service/runtime management + +Для `sing-box` как background runtime: + +- использовать `sing-box check` перед применением config; +- хранить config отдельно; +- запускать как Windows service или scheduled task; +- для service wrapper можно использовать WinSW, если не хочется писать собственный Windows service wrapper. + +Практичный вариант: + +- v1: WinSW wraps `sing-box.exe`; +- v2: собственный Rust service/helper, если понадобится полный контроль. + +Control App не должен напрямую владеть процессом `sing-box`. Он должен управлять service/task через helper. + +### Local sing-box + +`sing-box` - опциональный runtime. + +Его роль: + +- принять subscription/VLESS/sing-box config; +- поднять локальный mixed SOCKS/HTTP inbound; +- слушать только `127.0.0.1`, например `127.0.0.1:1080`; +- маршрутизировать трафик через выбранный outbound. + +Config генерируется из source state приложения и проверяется: + +```powershell +sing-box check -c C:\Tools\VpnProxy\sing-box\config.json +``` + +Local sing-box не должен быть обязательным. Если profile target указывает на внешний proxy, `sing-box` может отсутствовать. + +### Proxyfier layer + +Рекомендуемый стартовый backend: **ProxiFyre**. + +Почему: + +- open-source; +- Windows-focused; +- маршрутизирует TCP и UDP; +- работает per-application; +- использует `app-config.json`; +- может работать как Windows Service. + +Важное ограничение: ProxiFyre лицензируется как AGPL-3.0. Если продукт должен быть закрытым коммерческим приложением, нужно заранее решить юридический вопрос или сделать adapter layer, чтобы можно было заменить engine на: + +- коммерческий Proxifier; +- ProxyBridge; +- собственный WinDivert/NDIS/WFP-based engine; +- другой per-app proxy router. + +Интерфейс должен называться не `ProxiFyreConfig`, а шире: + +```text +ProxyRouterAdapter +``` + +Первый adapter: + +```text +ProxiFyreAdapter +``` + +Это позволит поменять engine без переделки UI и профилей. + +### Data storage + +Для MVP лучше использовать простые JSON-файлы с schema validation. + +Причина: + +- настройки легко читать и бэкапить; +- можно быстро отлаживать; +- config portable; +- подходит для profile/target/source state. + +Рекомендуемые файлы: + +```text +C:\ProgramData\VpnProxy\config\profiles.json +C:\ProgramData\VpnProxy\config\targets.json +C:\ProgramData\VpnProxy\config\components.json +C:\ProgramData\VpnProxy\state\activity.json +C:\ProgramData\VpnProxy\state\last-status.json +C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json +C:\ProgramData\VpnProxy\generated\sing-box-config.json +``` + +Если нужна большая история событий, статистика трафика или сложные миграции, тогда добавить SQLite: + +- `rusqlite` или `sqlx` в Rust; +- миграции; +- таблицы `activity`, `component_status`, `traffic_events`. + +Но source of truth для профилей можно оставить JSON даже при наличии SQLite. + +### Installer strategy + +Нужны три явных installer entrypoints: + +```text +Install Control App +Install Proxyfier Layer +Install Local sing-box +``` + +Они могут быть кнопками в UI, но каждая операция должна быть отдельной и понятной. + +CLI/script names: + +```text +install-control-app.ps1 +install-proxyfier.ps1 +install-singbox.ps1 +``` + +Или в packaged app: + +```text +VpnProxySetup.exe /component control-app +VpnProxySetup.exe /component proxyfier +VpnProxySetup.exe /component sing-box +``` + +Каждый installer: + +- idempotent; +- делает backup перед overwrite; +- не удаляет чужие файлы без подтверждения; +- проверяет admin rights; +- пишет machine-readable install result; +- не трогает остальные компоненты без явного выбора. + +### Security model + +Правила: + +- UI работает без admin rights. +- Admin elevation только для install/repair/service/config apply, если это реально нужно. +- Local API, если будет, слушает только `127.0.0.1`. +- Лучше использовать Tauri commands / named pipe, чем открытый HTTP port. +- Если нужен loopback HTTP, включить token или origin check. +- Секреты subscription URLs не показывать в diagnostics. +- Generated configs не редактируются вручную из UI. +- Every apply creates backup. + +## Архитектура + +```text ++-------------------------------+ +| Tauri Control App | +| React/TypeScript UI | ++---------------+---------------+ + | + v ++-------------------------------+ +| Rust App Backend | +| profiles, targets, validation | +| component status aggregation | ++-------+---------------+-------+ + | | + v v ++---------------+ +-------------------+ +| Proxy Router | | Local sing-box | +| Adapter | | Adapter | +| ProxiFyre v1 | | config + service | ++-------+-------+ +---------+---------+ + | | + v v ++---------------+ +-------------------+ +| ProxiFyre | | sing-box.exe | +| Windows svc | | Windows svc/task | ++---------------+ +-------------------+ +``` + +## Модель данных + +### Profile + +```json +{ + "id": "discord", + "name": "Discord", + "enabled": true, + "targetId": "local-singbox", + "protocols": ["TCP", "UDP"], + "items": [ + { "type": "process", "value": "Discord" }, + { "type": "folder", "value": "%LOCALAPPDATA%\\Discord", "recursive": true }, + { "type": "exe", "value": "C:\\Games\\Game\\game.exe" } + ] +} +``` + +### Target + +```json +{ + "id": "local-singbox", + "name": "Local sing-box", + "type": "local", + "protocol": "socks5", + "host": "127.0.0.1", + "port": 1080, + "requiresComponent": "singbox" +} +``` + +External target: + +```json +{ + "id": "home-gateway", + "name": "Home gateway", + "type": "external", + "protocol": "socks5", + "host": "192.168.50.111", + "port": 8080 +} +``` + +### Component status + +```json +{ + "id": "proxyfier", + "name": "Proxyfier", + "installed": true, + "running": true, + "version": "2.3.0", + "path": "C:\\Tools\\ProxiFyre", + "serviceName": "ProxiFyreService", + "problems": [], + "actions": ["restart", "repair", "openLogs"] +} +``` + +## Apply behavior + +Apply должен делать одно понятное действие: + +1. Прочитать profiles. +2. Прочитать targets. +3. Проверить, что выбранные targets доступны. +4. Проверить, что Proxyfier установлен. +5. Разрешить folder/exe в process names. +6. Сгенерировать proxyfier config. +7. Сделать backup старого config. +8. Записать новый config. +9. Перезапустить Proxyfier service. +10. Записать activity entry. + +Если profile использует `local-singbox`, дополнительно: + +- проверить, что `sing-box` установлен; +- проверить, что service running; +- проверить, что `127.0.0.1:1080` отвечает. + +Если `local-singbox` не установлен, но profile target внешний, apply должен работать. + +## Что не делать + +- Не делать глобальную смену Windows proxy settings. +- Не делать `sing-box` обязательным. +- Не смешивать installer и profile apply. +- Не хранить generated ProxiFyre config как source of truth. +- Не привязывать UI напрямую к ProxiFyre, нужен adapter layer. +- Не запускать privileged операции без явного согласия пользователя. +- Не делать большой dashboard с лишней статистикой в первой версии. + +## MVP + +Самый правильный первый slice: + +1. Tauri app shell. +2. Profiles UI. +3. Targets UI. +4. Component status UI. +5. ProxiFyre adapter. +6. External SOCKS5 target. +7. Apply profile -> generate ProxiFyre config -> restart service. + +В MVP `sing-box` может быть только карточкой `Not installed / Install`. + +После этого добавить: + +1. Local sing-box installer. +2. Subscription import. +3. Server selection. +4. Generate sing-box config. +5. Start/stop/restart local sing-box service. + +## Acceptance criteria + +Приложение считается успешным, если: + +- можно установить только Control App; +- можно установить Proxyfier отдельно; +- можно не устанавливать sing-box; +- можно добавить внешний SOCKS5 target; +- можно создать профиль для Discord; +- можно применить профиль; +- generated ProxiFyre config не редактируется пользователем вручную; +- UI показывает, что local sing-box отсутствует, но это не ломает внешний proxy flow; +- после установки sing-box появляется target `Local sing-box`; +- пользователь может переключить профиль с внешнего target на local sing-box. + +## Prompt For Another AI + +Build a Windows desktop proxy management app. + +Use Tauri 2 with React, TypeScript, Vite, and a Rust backend. The app must manage three independent components: the Control App, a proxyfier layer, and optional local sing-box. Do not make sing-box mandatory. + +The UI must be a compact Windows utility with these screens: Overview, Profiles, Targets, Components, Logs. Profiles contain process/folder/exe entries and choose a proxy target. Targets can be local sing-box or external SOCKS5/HTTP proxies. Proxyfier is the layer that routes selected apps through the chosen target. + +Start with ProxiFyre as the first proxy router adapter, but design an adapter boundary so it can later be replaced. Store source configuration as JSON with schema validation. Generated ProxiFyre and sing-box configs are derived artifacts, not source truth. + +Privileged operations must be isolated in an explicit helper/installer flow. The main UI should run without admin rights. Install Control App, Install Proxyfier, and Install Local sing-box must be separate operations. Applying a profile must not silently install missing components. + +MVP: external SOCKS5 target + ProxiFyre profile apply. Then add optional local sing-box installation, subscription import, server selection, and local sing-box service control. + +## References + +- Tauri 2: https://v2.tauri.app/ +- Tauri sidecar permissions: https://v2.tauri.app/develop/sidecar/ +- sing-box configuration: https://sing-box.sagernet.org/configuration/ +- ProxiFyre repository: https://github.com/wiresock/proxifyre +- WinSW service wrapper: https://github.com/winsw/winsw +- Microsoft Windows Service with Worker Service: https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service +