diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b49d680..953389b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,20 +192,20 @@ jobs: version: "10" - name: 安装依赖 - working-directory: sandbox-web + working-directory: electron-app run: pnpm install --frozen-lockfile - name: TypeScript 类型检查 - working-directory: sandbox-web + working-directory: electron-app run: pnpm typecheck - name: 运行前端测试 + 覆盖率 - working-directory: sandbox-web + working-directory: electron-app run: pnpm vitest run --coverage --coverage.reporter=json-summary --coverage.reporter=text - name: 生成前端覆盖率摘要 if: always() - working-directory: sandbox-web + working-directory: electron-app run: | SUMMARY_FILE="frontend-coverage-summary.md" echo "## 前端测试覆盖率" > "$SUMMARY_FILE" @@ -260,7 +260,7 @@ jobs: if: always() with: name: frontend-coverage-summary - path: sandbox-web/frontend-coverage-summary.md + path: electron-app/frontend-coverage-summary.md retention-days: 1 - name: 上传前端覆盖率报告 @@ -268,7 +268,7 @@ jobs: if: always() with: name: frontend-coverage - path: sandbox-web/coverage/ + path: electron-app/coverage/ retention-days: 14 # ==================== 安全检查 ==================== diff --git a/CLAUDE.md b/CLAUDE.md index 1b37ecd..a47df66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,62 +4,33 @@ > > 对目标应用**零侵入**,所有操作在 OS 层面完成(CGEvent + AXUIElement + ScreenCaptureKit)。 > -> **多实例架构**:每个沙箱是一个独立的 Tauri 窗口进程,拥有唯一 ID、内嵌 HTTP API 服务器,通过文件系统注册中心(`~/.sandbox/instances/`)进行实例发现和管理。 +> **Daemon + Electron 架构**:一个长驻 Rust daemon 管理所有沙箱实例(PTY 进程 + macOS 应用),通过单一 HTTP API 对外服务。Electron 应用作为 GUI 前端,提供 tab 管理、xterm.js 终端和截图预览。CLI 直接与 daemon 通信。 ## 一、架构总览 ``` ┌──────────────────────────────────────────────────────────────┐ │ Agent / 用户 (CLI / MCP / HTTP) │ -│ │ -│ sandbox start → 返回 sandbox-id │ -│ sandbox list → 列出所有实例 │ -│ sandbox screenshot → 截取沙箱截图 │ -│ sandbox click 100 200 → 模拟点击 │ -│ sandbox close → 关闭沙箱 │ -└──────────────────────┬───────────────────────────────────────┘ - │ CLI (子进程启动) / MCP stdio / HTTP - ▼ +│ sandbox start / list / screenshot / click / type / key │ +└───────────────────────────────┬───────────────────────────────┘ + │ HTTP (localhost:15801) + ▼ ┌──────────────────────────────────────────────────────────────┐ -│ 沙箱实例注册中心 (~/.sandbox/instances/) │ -│ │ -│ ┌─────────────────────┐ ┌─────────────────────┐ │ -│ │ Sandbox Instance #1 │ │ Sandbox Instance #2 │ ... │ -│ │ id: abc123 │ │ id: def456 │ │ -│ │ port: 15801 │ │ port: 15802 │ │ -│ │ mode: cli (claude) │ │ mode: app (cc-switch)│ │ -│ │ status: Running │ │ status: Running │ │ -│ └─────────┬───────────┘ └─────────┬───────────┘ │ -│ │ │ │ -│ │ HTTP :15801 │ HTTP :15802 │ -└────────────┼─────────────────────────┼────────────────────────┘ - │ │ - ▼ ▼ - ┌──────────────────┐ ┌──────────────────┐ - │ Tauri Window #1 │ │ Tauri Window #2 │ - │ "System Test │ │ "System Test │ - │ Sandbox [abc]" │ │ Sandbox [def]" │ - │ │ │ │ - │ ┌────────────┐ │ │ ┌────────────┐ │ - │ │ xterm.js │ │ │ │ App 关联 │ │ - │ │ (claude) │ │ │ │ (cc-switch)│ │ - │ └────────────┘ │ │ └────────────┘ │ - │ │ │ │ - │ 内嵌 HTTP API │ │ 内嵌 HTTP API │ - │ + Automation │ │ + Automation │ - │ Engine │ │ Engine │ - └──────────────────┘ └──────────────────┘ - │ │ - ▼ ▼ - ┌─────────────┐ ┌─────────────────┐ - │ CLI 进程 │ │ macOS .app │ - │ (PTY) │ │ (NSWorkspace) │ - └─────────────┘ └─────────────────┘ +│ sandbox-daemon (Rust, 单实例) │ +│ PTY Manager + App Manager + Automation Engine │ +│ Instance Registry (~/.sandbox/instances/) │ +└───────────────────────────────┬───────────────────────────────┘ + │ WebSocket (PTY 流) + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Electron App (单实例, Chromium) │ +│ Tab 管理 + xterm.js + 控制面板 + 截图预览 │ +└──────────────────────────────────────────────────────────────┘ ``` **设计原则**: 1. **零侵入**:目标应用不需要任何适配,所有操作在 OS 层面完成 -2. **多实例**:每个沙箱是独立的 Tauri 窗口进程,通过 CLI 管理生命周期 +2. **单 daemon 多沙箱**:一个 daemon 进程管理所有沙箱实例,通过 CLI 管理生命周期 3. **窗口级截图**:ScreenCaptureKit 按窗口 ID 截图,不需要窗口在前台 4. **双协议**:MCP (Agent CLI 原生) + HTTP (通用调用) 5. **文件系统注册中心**:沙箱实例通过 `~/.sandbox/instances/.json` 注册和发现 @@ -72,8 +43,8 @@ |---------|--------| | 核心库 | Rust (Edition 2021, >=1.88), `sandbox-core` library crate | | CLI | Rust, `sandbox-cli` binary crate | -| 桌面框架 | Tauri 2.x | -| 桌面前端 | React 18 + TS + Vite + TailwindCSS + xterm.js | +| 桌面框架 | Electron (Chromium) | +| 桌面前端 | React 18 + TS + Vite + xterm.js | | 异步运行时 | tokio | | macOS API | CoreGraphics (CGEvent), ApplicationServices (AXUIElement), ScreenCaptureKit | | 包管理 | Cargo Workspace + pnpm | diff --git a/Cargo.lock b/Cargo.lock index 9e91dcc..d5b5d9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,30 +47,6 @@ dependencies = [ "equator", ] -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -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 = "anstream" version = "1.0.0" @@ -141,7 +117,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -167,30 +143,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[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", + "syn", ] [[package]] @@ -255,7 +208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", - "base64 0.22.1", + "base64", "bytes", "form_urlencoded", "futures-util", @@ -303,33 +256,12 @@ dependencies = [ "tracing", ] -[[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 = "bit_field" version = "0.10.3" @@ -347,9 +279,6 @@ name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -dependencies = [ - "serde_core", -] [[package]] name = "bitstream-io" @@ -369,45 +298,6 @@ 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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -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 = "built" version = "0.8.0" @@ -426,12 +316,6 @@ 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 = "byteorder-lite" version = "0.1.0" @@ -443,76 +327,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -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.11.1", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -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" @@ -526,33 +340,6 @@ dependencies = [ "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" @@ -571,18 +358,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.1", -] - [[package]] name = "clap" version = "4.6.1" @@ -611,10 +386,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -635,26 +410,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[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.9.4" @@ -774,393 +529,134 @@ dependencies = [ ] [[package]] -name = "cssparser" -version = "0.36.0" +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", + "powerfmt", ] [[package]] -name = "cssparser-macros" -version = "0.6.1" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "quote", - "syn 2.0.117", + "block-buffer", + "crypto-common", ] [[package]] -name = "ctor" -version = "0.8.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "ctor-proc-macro", - "dtor", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "ctor-proc-macro" -version = "0.0.7" +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "darling" -version = "0.23.0" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "darling_core", - "darling_macro", + "cfg-if", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "equator" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "equator-macro", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "equator-macro" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ - "darling_core", + "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] -name = "data-encoding" -version = "2.11.0" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "dbus" -version = "0.9.11" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "libdbus-sys", "windows-sys 0.61.2", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "exr" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" dependencies = [ - "powerfmt", - "serde_core", + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", ] [[package]] -name = "derive_more" -version = "2.1.1" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "derive_more-impl" -version = "2.1.1" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "digest" -version = "0.10.7" +name = "fastrand" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[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.11.1", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[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.117", -] - -[[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 0.2.0", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[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 = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "embed-resource" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.2+spec-1.1.0", - "vswhom", - "winreg 0.55.0", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[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 = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "exr" -version = "1.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fax" -version = "0.2.7" +name = "fax" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" @@ -1173,16 +669,6 @@ 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 = "filedescriptor" version = "0.8.3" @@ -1222,12 +708,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[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.3.2" @@ -1255,7 +735,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1294,23 +774,6 @@ 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" @@ -1319,7 +782,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1341,323 +804,74 @@ 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" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", + "typenum", + "version_check", ] [[package]] -name = "gdk-pixbuf" -version = "0.18.5" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", + "cfg-if", "libc", - "once_cell", + "wasi", ] [[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "cfg-if", "libc", - "system-deps", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "cfg-if", "libc", - "pango-sys", - "pkg-config", - "system-deps", + "r-efi 6.0.0", + "wasip2", + "wasip3", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "gif" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", + "color_quant", + "weezl", ] [[package]] -name = "gdkx11" -version = "0.18.2" +name = "h2" +version = "0.4.14" 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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - -[[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.11.1", - "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.117", -] - -[[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.117", -] - -[[package]] -name = "h2" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1665,7 +879,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.14.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -1683,12 +897,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1704,7 +912,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -1722,34 +930,12 @@ dependencies = [ "hashbrown 0.14.5", ] -[[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.0" @@ -1854,7 +1040,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1873,40 +1059,6 @@ dependencies = [ "windows-registry", ] -[[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" @@ -1995,12 +1147,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" -[[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" @@ -2036,7 +1182,7 @@ dependencies = [ "image-webp", "moxcms", "num-traits", - "png 0.18.1", + "png", "qoi", "ravif", "rayon", @@ -2062,17 +1208,6 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" -[[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" @@ -2085,15 +1220,6 @@ dependencies = [ "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 = "interpolate_name" version = "0.2.4" @@ -2102,7 +1228,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2111,25 +1237,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2152,184 +1259,51 @@ 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" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "glib-sys", - "gobject-sys", + "getrandom 0.3.4", "libc", - "system-deps", ] [[package]] -name = "jni" -version = "0.21.1" +name = "js-sys" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ - "cesu8", "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "futures-util", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "jni-sys" -version = "0.4.1" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[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.117", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "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.11.1", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "lebe" -version = "0.5.3" +name = "lebe" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" -[[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 = "libfuzzer-sys" version = "0.4.12" @@ -2340,25 +1314,6 @@ dependencies = [ "cc", ] -[[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.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "libc", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -2415,17 +1370,6 @@ dependencies = [ "libc", ] -[[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 = "matchers" version = "0.2.0" @@ -2457,15 +1401,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[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" @@ -2503,27 +1438,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "muda" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" -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 = "native-tls" version = "0.2.18" @@ -2541,30 +1455,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.1", - "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" @@ -2652,7 +1542,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2684,28 +1574,6 @@ 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.117", -] - [[package]] name = "objc" version = "0.2.7" @@ -2716,3430 +1584,1555 @@ dependencies = [ ] [[package]] -name = "objc2" -version = "0.6.4" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "objc2-app-kit" -version = "0.3.2" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "objc2-cloud-kit" -version = "0.3.2" +name = "openssl" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags 2.11.1", - "objc2", - "objc2-foundation", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "objc2-core-data" -version = "0.3.2" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "objc2", - "objc2-foundation", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "objc2-core-foundation" -version = "0.3.2" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.1", - "dispatch2", - "objc2", -] +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "objc2-core-graphics" -version = "0.3.2" +name = "openssl-sys" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ - "bitflags 2.11.1", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "objc2-core-image" -version = "0.3.2" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "objc2", - "objc2-foundation", + "lock_api", + "parking_lot_core", ] [[package]] -name = "objc2-core-location" -version = "0.3.2" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "objc2", - "objc2-foundation", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", ] [[package]] -name = "objc2-core-text" -version = "0.3.2" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "objc2-encode" -version = "4.1.0" +name = "pastey" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] -name = "objc2-exception-helper" -version = "0.1.1" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "objc2-foundation" -version = "0.3.2" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "pkg-config" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", -] +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] -name = "objc2-quartz-core" -version = "0.3.2" +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.11.1", - "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" +name = "portable-pty" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" 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.11.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.28.0", + "serial2", + "shared_library", + "shell-words", + "winapi", + "winreg", ] [[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "open" -version = "5.3.5" +name = "potential_utf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", + "zerovec", ] [[package]] -name = "openssl" -version = "0.10.80" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "openssl-macros", - "openssl-sys", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "zerocopy", ] [[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.116" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "proc-macro2", + "syn", ] [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_pipe" -version = "1.2.3" +name = "proc-macro2" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "libc", - "windows-sys 0.61.2", + "unicode-ident", ] [[package]] -name = "pango" -version = "0.18.3" +name = "profiling" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", + "profiling-procmacros", ] [[package]] -name = "pango-sys" -version = "0.18.0" +name = "profiling-procmacros" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "quote", + "syn", ] [[package]] -name = "parking_lot" -version = "0.12.5" +name = "pxfm" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "qoi" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", + "bytemuck", ] [[package]] -name = "paste" -version = "1.0.15" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] -name = "pastey" -version = "0.1.1" +name = "quote" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] [[package]] -name = "pathdiff" -version = "0.2.3" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "phf" -version = "0.13.1" +name = "rand" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "phf_macros", - "phf_shared", - "serde", + "rand_chacha", + "rand_core", ] [[package]] -name = "phf_codegen" -version = "0.13.1" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "phf_generator", - "phf_shared", + "ppv-lite86", + "rand_core", ] [[package]] -name = "phf_generator" -version = "0.13.1" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "fastrand", - "phf_shared", + "getrandom 0.3.4", ] [[package]] -name = "phf_macros" -version = "0.13.1" +name = "rav1e" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.117", + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", ] [[package]] -name = "phf_shared" -version = "0.13.1" +name = "ravif" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" dependencies = [ - "siphasher", + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", ] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "rayon" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] [[package]] -name = "pkg-config" -version = "0.3.33" +name = "rayon-core" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] [[package]] -name = "plist" -version = "1.9.0" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml", - "serde", - "time", + "bitflags 2.11.1", ] [[package]] -name = "png" -version = "0.17.16" +name = "regex-automata" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "png" -version = "0.18.1" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.11.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "portable-pty" -version = "0.9.0" +name = "reqwest" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "downcast-rs", - "filedescriptor", - "lazy_static", - "libc", - "log", - "nix 0.28.0", - "serial2", - "shared_library", - "shell-words", - "winapi", - "winreg 0.10.1", -] - -[[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 = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - -[[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.11+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 = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pxfm" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[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.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -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 = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand", - "rand_chacha", - "simd_helpers", - "thiserror 2.0.18", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.1", -] - -[[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.117", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -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.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "reqwest" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" -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 = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rusqlite" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" -dependencies = [ - "bitflags 2.11.1", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[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 = "sandbox-cli" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "futures-util", - "reqwest 0.12.28", - "sandbox-core", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite", - "tracing", -] - -[[package]] -name = "sandbox-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "axum", - "base64 0.22.1", - "core-foundation 0.10.1", - "core-graphics", - "futures-util", - "image", - "libc", - "nix 0.29.0", - "objc", - "portable-pty", - "rusqlite", - "screencapturekit", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tower", - "tower-http", - "tracing", - "tracing-appender", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[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.117", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "screencapturekit" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c26aa93dbf2f4edfc3646a9b9c4ab0d7acfb1b0e5938fe2d8d1d9a4e66b3502" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" -dependencies = [ - "bitflags 2.11.1", - "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.117", -] - -[[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.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[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.117", -] - -[[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_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" -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.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serial2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb6ea5562eeaed6936b8b54e086aa0f88b9e5b1bef45beb038e2519fa1185b1" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.61.2", -] - -[[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.117", -] - -[[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 = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[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 = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared_child" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" -dependencies = [ - "libc", - "sigchld", - "windows-sys 0.60.2", -] - -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "sigchld" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" -dependencies = [ - "libc", - "os_pipe", - "signal-hook", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -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 = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[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 = "symlink" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" - -[[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.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -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.117", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[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 = "system-test-sandbox" -version = "0.1.0" -dependencies = [ - "axum", - "sandbox-core", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-shell", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "tao" -version = "0.35.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4" -dependencies = [ - "bitflags 2.11.1", - "block2", - "core-foundation 0.10.1", - "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.117", -] - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" -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 0.13.3", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" -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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" -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.117", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "walkdir", -] - -[[package]] -name = "tauri-plugin-shell" -version = "2.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" -dependencies = [ - "encoding_rs", - "log", - "open", - "os_pipe", - "regex", - "schemars 0.8.22", - "serde", - "serde_json", - "shared_child", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-runtime" -version = "2.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" -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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "gtk", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", "http", - "jni", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", "log", - "objc2", - "objc2-app-kit", - "once_cell", + "mime", + "native-tls", "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" -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", + "pin-project-lite", + "rustls-pki-types", "serde", - "serde-untagged", "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 1.1.2+spec-1.1.0", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", "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 = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[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.117", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "thiserror-impl" -version = "2.0.18" +name = "rgb" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" [[package]] -name = "thread_local" -version = "1.1.9" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ + "cc", "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg", -] - -[[package]] -name = "time" -version = "0.3.47" +name = "rusqlite" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", ] [[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "num-conv", - "time-core", + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "tinystr" -version = "0.8.3" +name = "rustls" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "displaydoc", - "zerovec", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "tinyvec" -version = "1.11.0" +name = "rustls-pki-types" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "tinyvec_macros", + "zeroize", ] [[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" +name = "rustls-webpki" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "tokio-macros" -version = "2.7.0" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "ryu" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +name = "sandbox-cli" +version = "0.1.0" dependencies = [ - "rustls", + "anyhow", + "base64", + "clap", + "futures-util", + "reqwest", + "sandbox-core", + "serde", + "serde_json", "tokio", + "tokio-tungstenite", + "tracing", ] [[package]] -name = "tokio-tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +name = "sandbox-core" +version = "0.1.0" dependencies = [ + "async-trait", + "axum", + "base64", + "core-foundation 0.10.1", + "core-graphics", "futures-util", - "log", + "image", + "libc", + "nix 0.29.0", + "objc", + "portable-pty", + "rusqlite", + "screencapturekit", + "serde", + "serde_json", + "thiserror 2.0.18", "tokio", - "tungstenite", + "tower", + "tower-http", + "tracing", + "tracing-appender", + "tracing-subscriber", + "uuid", ] [[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +name = "sandbox-daemon" +version = "0.1.0" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", + "sandbox-core", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] -name = "toml" -version = "0.8.2" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "windows-sys 0.61.2", ] [[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" +name = "scopeguard" +version = "1.2.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", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" +name = "screencapturekit" +version = "2.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", -] +checksum = "8c26aa93dbf2f4edfc3646a9b9c4ab0d7acfb1b0e5938fe2d8d1d9a4e66b3502" [[package]] -name = "toml_datetime" -version = "0.6.3" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "serde", + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ - "serde_core", + "core-foundation-sys", + "libc", ] [[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "toml_edit" -version = "0.19.15" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", + "serde_core", + "serde_derive", ] [[package]] -name = "toml_edit" -version = "0.20.2" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "indexmap 2.14.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", + "serde_derive", ] [[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.3", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "winnow 1.0.3", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[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" +name = "serde_path_to_error" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", + "itoa", + "serde", + "serde_core", ] [[package]] -name = "tower-http" -version = "0.6.11" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "bitflags 2.11.1", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", + "form_urlencoded", + "itoa", + "ryu", + "serde", ] [[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" +name = "serial2" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "9eb6ea5562eeaed6936b8b54e086aa0f88b9e5b1bef45beb038e2519fa1185b1" dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "cfg-if", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "tracing-appender" -version = "0.2.5" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "crossbeam-channel", - "parking_lot", - "symlink", - "thiserror 2.0.18", - "time", - "tracing-subscriber", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "tracing-attributes" -version = "0.1.31" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "lazy_static", ] [[package]] -name = "tracing-core" -version = "0.1.36" +name = "shared_library" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" dependencies = [ - "once_cell", - "valuable", + "lazy_static", + "libc", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "shell-words" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] -name = "tracing-subscriber" -version = "0.3.23" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "tray-icon" -version = "0.23.1" +name = "signal-hook-registry" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 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", + "errno", + "libc", ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "simd-adler32" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" -[[package]] -name = "tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand", - "sha1", - "thiserror 2.0.18", +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", ] [[package]] -name = "typeid" -version = "1.0.3" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "typenum" -version = "1.20.0" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "socket2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ - "unic-char-range", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "unic-char-range" -version = "0.9.0" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "unic-common" -version = "0.9.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "unic-ucd-ident" -version = "0.9.0" +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "unic-common", + "futures-core", ] [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "unicode-segmentation" -version = "1.13.2" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] -name = "untrusted" -version = "0.9.0" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] [[package]] -name = "url" -version = "2.5.8" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", + "thiserror-impl 1.0.69", ] [[package]] -name = "urlpattern" -version = "0.3.0" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", + "thiserror-impl 2.0.18", ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "utf8_iter" -version = "1.0.4" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "utf8parse" -version = "0.2.2" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] [[package]] -name = "uuid" -version = "1.23.1" +name = "tiff" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" dependencies = [ - "getrandom 0.4.2", - "js-sys", - "serde_core", - "wasm-bindgen", + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", ] [[package]] -name = "v_frame" -version = "0.3.9" +name = "time" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "valuable" -version = "0.1.1" +name = "time-core" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "time-macros" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] [[package]] -name = "version-compare" -version = "0.2.1" +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] -name = "version_check" -version = "0.9.5" +name = "tokio" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] [[package]] -name = "vswhom" -version = "0.1.0" +name = "tokio-macros" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ - "libc", - "vswhom-sys", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "vswhom-sys" -version = "0.1.3" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "cc", - "libc", + "native-tls", + "tokio", ] [[package]] -name = "walkdir" -version = "2.5.0" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "same-file", - "winapi-util", + "rustls", + "tokio", ] [[package]] -name = "want" -version = "0.3.1" +name = "tokio-tungstenite" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ - "try-lock", + "futures-util", + "log", + "tokio", + "tungstenite", ] [[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] [[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ - "wit-bindgen 0.57.1", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +name = "tower-http" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "wit-bindgen 0.51.0", + "bitflags 2.11.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", ] [[package]] -name = "wasm-bindgen" -version = "0.2.121" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] -name = "wasm-bindgen-futures" -version = "0.4.71" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "js-sys", - "wasm-bindgen", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.121" +name = "tracing-appender" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "crossbeam-channel", + "parking_lot", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.121" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", - "wasm-bindgen-shared", + "syn", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.121" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "unicode-ident", + "once_cell", + "valuable", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "leb128fmt", - "wasmparser", + "log", + "once_cell", + "tracing-core", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "tracing-subscriber" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] -name = "wasm-streams" -version = "0.5.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "wasmparser" -version = "0.244.0" +name = "tungstenite" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.18", ] [[package]] -name = "web-sys" -version = "0.3.98" +name = "typenum" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] -name = "web_atoms" -version = "0.2.4" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" -dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "webkit2gtk" -version = "2.0.2" +name = "unicode-xid" +version = "0.2.6" 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", -] +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "webkit2gtk-sys" -version = "2.0.2" +name = "untrusted" +version = "0.9.0" 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", -] +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "webview2-com" -version = "0.38.2" +name = "url" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "form_urlencoded", + "idna", + "percent-encoding", + "serde", ] [[package]] -name = "webview2-com-macros" -version = "0.8.1" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "webview2-com-sys" -version = "0.38.2" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows", - "windows-core 0.61.2", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "weezl" -version = "0.1.12" +name = "uuid" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] [[package]] -name = "winapi" +name = "v_frame" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "aligned-vec", + "num-traits", + "wasm-bindgen", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "winapi-util" -version = "0.1.11" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "window-vibrancy" -version = "0.6.0" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", + "try-lock", ] [[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", -] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "windows-collections" -version = "0.2.0" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "windows-core 0.61.2", + "wit-bindgen 0.57.1", ] [[package]] -name = "windows-core" -version = "0.61.2" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "wit-bindgen 0.51.0", ] [[package]] -name = "windows-core" -version = "0.62.2" +name = "wasm-bindgen" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "windows-future" -version = "0.2.1" +name = "wasm-bindgen-futures" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "windows-implement" -version = "0.60.2" +name = "wasm-bindgen-macro" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ - "proc-macro2", "quote", - "syn 2.0.117", + "wasm-bindgen-macro-support", ] [[package]] -name = "windows-interface" -version = "0.59.3" +name = "wasm-bindgen-macro-support" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", + "wasm-bindgen-shared", ] [[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" +name = "wasm-bindgen-shared" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "unicode-ident", ] [[package]] -name = "windows-registry" -version = "0.6.1" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "leb128fmt", + "wasmparser", ] [[package]] -name = "windows-result" -version = "0.3.4" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "windows-link 0.1.3", + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "windows-result" -version = "0.4.1" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "windows-link 0.2.1", + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] -name = "windows-strings" -version = "0.4.2" +name = "web-sys" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ - "windows-link 0.1.3", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "windows-strings" -version = "0.5.1" +name = "weezl" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] -name = "windows-sys" -version = "0.45.0" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "windows-targets 0.42.2", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.60.2" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-sys" -version = "0.61.2" +name = "windows-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 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", + "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "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", + "windows-link", ] [[package]] -name = "windows-targets" -version = "0.53.5" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows-targets", ] [[package]] -name = "windows-threading" -version = "0.1.0" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] -name = "windows-version" -version = "0.1.7" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows-link 0.2.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[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_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[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_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[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 = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[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.10.1" @@ -6149,16 +3142,6 @@ dependencies = [ "winapi", ] -[[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.51.0" @@ -6181,7 +3164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "wit-parser", ] @@ -6192,10 +3175,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", + "heck", + "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -6211,7 +3194,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -6224,7 +3207,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.1", - "indexmap 2.14.0", + "indexmap", "log", "serde", "serde_derive", @@ -6243,7 +3226,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.14.0", + "indexmap", "log", "semver", "serde", @@ -6259,71 +3242,6 @@ 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 = "y4m" version = "0.8.0" @@ -6349,7 +3267,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -6370,7 +3288,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -6390,7 +3308,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -6430,7 +3348,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c35f622..fd9dd14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = [ "crates/sandbox-core", "crates/sandbox-cli", - "src-tauri", + "crates/sandbox-daemon", ] [workspace.package] diff --git a/README.md b/README.md index 6a24cfb..5f70b78 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,18 @@ macOS 桌面自动化沙箱 — 支持多实例管理,通过 CLI 命令启动 ## 架构 ``` -sandbox-cli start --cli "claude" +sandbox start claude │ - ├─ 生成沙箱 ID, 分配端口 - ├─ 启动 Tauri 窗口进程 (含内嵌 HTTP API) - ├─ 在 xterm.js 终端中运行 claude (PTY) + ├─ 确保 daemon 运行 (sandbox-daemon, 端口 15801) + ├─ POST /sandbox/create → 创建 PTY 沙箱 + ├─ 启动 Electron 应用 (如未运行) └─ 写入注册中心 ~/.sandbox/instances/.json -sandbox-cli screenshot - ├─ 读取注册中心, 获取端口 - └─ GET http://127.0.0.1:/screenshot → PNG +sandbox screenshot --id + └─ GET http://127.0.0.1:15801/sandbox//screenshot → PNG -sandbox-cli close - ├─ 读取注册中心 - ├─ POST http://127.0.0.1:/shutdown +sandbox close + ├─ POST http://127.0.0.1:15801/sandbox//close └─ 清理注册信息, 终止关联进程 ``` @@ -39,12 +37,11 @@ sandbox-cli close git clone https://github.com/ZN-Ice/system-test-sandbox.git cd system-test-sandbox -# 构建 Release(推荐) -./release.sh +# 构建 daemon + CLI +cargo build --release -# 或手动构建 -cd sandbox-web && pnpm install && pnpm build && cd .. -cargo build --release -p sandbox-cli +# 构建 Electron 应用 +cd electron-app && pnpm install && pnpm build && cd .. ``` ### 启动沙箱 @@ -283,8 +280,8 @@ RUST_LOG=info ./System\ Test\ Sandbox.app/Contents/MacOS/system-test-sandbox --m |---------|--------| | 核心库 | Rust (Edition 2021, >=1.88), `sandbox-core` library crate | | CLI | Rust, `sandbox-cli` binary crate | -| 桌面框架 | Tauri 2.x | -| 桌面前端 | React 18 + TS + Vite + TailwindCSS + xterm.js | +| 桌面框架 | Electron (Chromium) | +| 桌面前端 | React 18 + TS + Vite + xterm.js | | 异步运行时 | tokio | | macOS API | CoreGraphics (CGEvent), ApplicationServices (AXUIElement), ScreenCaptureKit | | 包管理 | Cargo Workspace + pnpm | @@ -303,19 +300,17 @@ system-test-sandbox/ │ │ ├── automation/ # CGEvent + AXUIElement │ │ ├── capture/ # ScreenCaptureKit 截图 │ │ ├── process/ # PTY + NSWorkspace 进程管理 -│ │ ├── sandbox/ # 沙箱窗口管理 (多实例) +│ │ ├── daemon/ # HTTP daemon (单实例管理所有沙箱) │ │ ├── instance/ # 实例注册中心 │ │ └── server/ # HTTP API 服务器 │ └── sandbox-cli/ # CLI 工具 │ └── src/ -│ ├── main.rs # start/list/close + 子命令 -│ ├── client.rs # HTTP 客户端 -│ └── mcp_server.rs # MCP 服务器 -├── sandbox-web/ # 沙箱窗口前端 -│ └── src/ +│ ├── main.rs # start/list/close + MCP stdio +│ └── client.rs # HTTP 客户端 +├── electron-app/ # Electron GUI 前端 +│ └── src/renderer/ │ ├── main.tsx, api.ts -│ └── components/ -├── src-tauri/ # macOS 宿主应用 (Tauri) +│ └── components/ # Terminal, AppPanel └── docs/ # 设计文档 + 任务管理 ``` diff --git a/crates/sandbox-cli/Cargo.toml b/crates/sandbox-cli/Cargo.toml index 7224ba0..1f8390e 100644 --- a/crates/sandbox-cli/Cargo.toml +++ b/crates/sandbox-cli/Cargo.toml @@ -21,3 +21,4 @@ serde_json.workspace = true tracing.workspace = true tokio-tungstenite.workspace = true futures-util.workspace = true +base64.workspace = true diff --git a/crates/sandbox-cli/src/client.rs b/crates/sandbox-cli/src/client.rs index f2b8de5..a9ec916 100644 --- a/crates/sandbox-cli/src/client.rs +++ b/crates/sandbox-cli/src/client.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; /// Check if debug logging is enabled via SANDBOX_LOGGER_LEVEL=debug fn debug_enabled() -> bool { @@ -16,6 +16,323 @@ macro_rules! debug_log { }; } +// ── Daemon discovery helpers ────────────────────────────────── + +/// Resolve the daemon port from `daemon.json`. Errors if daemon is not running. +pub fn resolve_daemon_port() -> Result { + sandbox_core::daemon::find_running_daemon() + .with_context(|| "Sandbox daemon is not running. Start it with: sandbox start ") +} + +/// Returns `http://127.0.0.1:{port}` for the running daemon. +pub fn daemon_base_url() -> Result { + let port = resolve_daemon_port()?; + Ok(format!("http://127.0.0.1:{port}")) +} + +// ── Daemon API response types ───────────────────────────────── + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct DaemonHealthResponse { + pub status: String, + pub version: String, + pub uptime_secs: u64, + pub sandboxes: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DaemonSandbox { + pub id: String, + pub kind: sandbox_core::instance::InstanceKind, + pub status: sandbox_core::instance::InstanceStatus, + pub port: u16, + pub pty_pid: Option, + pub window_id: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CreateSandboxResponse { + pub sandbox_id: String, + pub pty_pid: Option, + pub window_id: Option, +} + +// ── Daemon API commands ─────────────────────────────────────── + +/// Create a new sandbox via the daemon HTTP API. +pub async fn daemon_create_sandbox( + mode: &str, + command: Option<&str>, + args: &[String], + cols: Option, + rows: Option, +) -> Result { + let base = daemon_base_url()?; + let client = reqwest_client(); + let body = serde_json::json!({ + "mode": mode, + "command": command, + "args": args, + "cols": cols, + "rows": rows, + }); + let resp = client + .post(format!("{base}/sandbox/create")) + .json(&body) + .send() + .await + .with_context(|| "Failed to connect to sandbox daemon")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("Daemon create failed (HTTP {status}): {text}"); + } + let result: CreateSandboxResponse = resp.json().await?; + Ok(result) +} + +/// List all sandboxes via the daemon HTTP API. +pub async fn daemon_list_sandboxes() -> Result> { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!("{base}/sandbox/list")) + .send() + .await + .with_context(|| "Failed to connect to sandbox daemon")?; + let list: Vec = resp.json().await?; + Ok(list) +} + +/// Take a screenshot of a sandbox via the daemon HTTP API. Returns PNG bytes. +pub async fn daemon_screenshot(sandbox_id: &str) -> Result> { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!("{base}/sandbox/{sandbox_id}/screenshot")) + .send() + .await + .with_context(|| "screenshot request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("screenshot failed (HTTP {status}): {text}"); + } + let bytes = resp.bytes().await?.to_vec(); + Ok(bytes) +} + +/// Click in a sandbox via the daemon HTTP API. +pub async fn daemon_click(sandbox_id: &str, x: f64, y: f64, button: &str) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/sandbox/{sandbox_id}/input/click")) + .json(&serde_json::json!({ "x": x, "y": y, "button": button })) + .send() + .await + .with_context(|| "click request to daemon failed")? + .error_for_status() + .with_context(|| "click failed")?; + Ok(()) +} + +/// Type text in a sandbox via the daemon HTTP API. +pub async fn daemon_type(sandbox_id: &str, text: &str) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/sandbox/{sandbox_id}/input/type")) + .json(&serde_json::json!({ "text": text })) + .send() + .await + .with_context(|| "type request to daemon failed")? + .error_for_status() + .with_context(|| "type failed")?; + Ok(()) +} + +/// Type text into a sandbox PTY via the daemon HTTP API. +pub async fn daemon_pty_write(sandbox_id: &str, data: &str) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/sandbox/{sandbox_id}/pty/write")) + .json(&serde_json::json!({ "data": data })) + .send() + .await + .with_context(|| "pty_write request to daemon failed")? + .error_for_status() + .with_context(|| "pty_write failed")?; + Ok(()) +} + +/// Press a key in a sandbox via the daemon HTTP API. +pub async fn daemon_key(sandbox_id: &str, key: &str, modifiers: &[String]) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/sandbox/{sandbox_id}/input/key")) + .json(&serde_json::json!({ "key": key, "modifiers": modifiers })) + .send() + .await + .with_context(|| "key request to daemon failed")? + .error_for_status() + .with_context(|| "key failed")?; + Ok(()) +} + +/// Close a sandbox via the daemon HTTP API. +pub async fn daemon_close(sandbox_id: &str) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/sandbox/{sandbox_id}/close")) + .send() + .await + .with_context(|| "close request to daemon failed")? + .error_for_status() + .with_context(|| "close failed")?; + Ok(()) +} + +/// Shut down the daemon via HTTP. +pub async fn daemon_shutdown() -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + client + .post(format!("{base}/shutdown")) + .send() + .await + .with_context(|| "shutdown request to daemon failed")? + .error_for_status() + .with_context(|| "daemon shutdown failed")?; + Ok(()) +} + +/// Inspect a sandbox via the daemon API. Returns sandbox info from the daemon's in-memory state. +pub async fn daemon_inspect(sandbox_id: &str) -> Result { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!("{base}/sandbox/list")) + .send() + .await + .with_context(|| "Failed to fetch sandbox list from daemon")?; + let sandboxes: Vec = resp.json().await?; + sandboxes + .into_iter() + .find(|sb| sb.id == sandbox_id) + .with_context(|| format!("Sandbox '{sandbox_id}' not found in daemon")) +} + +/// List processes in a sandbox via the daemon HTTP API. +pub async fn daemon_processes(sandbox_id: &str) -> Result> { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!("{base}/sandbox/{sandbox_id}/processes")) + .send() + .await + .with_context(|| "processes request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("processes failed (HTTP {status}): {text}"); + } + let processes: Vec = resp.json().await?; + Ok(processes) +} + +/// Inspect UI tree of a sandbox window via the daemon HTTP API. +pub async fn daemon_ui_inspect(sandbox_id: &str) -> Result { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!("{base}/sandbox/{sandbox_id}/ui/inspect")) + .send() + .await + .with_context(|| "ui/inspect request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("ui/inspect failed (HTTP {status}): {text}"); + } + Ok(resp.json().await?) +} + +/// Find UI elements by role/title in a sandbox window. +pub async fn daemon_ui_find( + sandbox_id: &str, + role: &str, + title: Option<&str>, +) -> Result { + let base = daemon_base_url()?; + let client = reqwest_client(); + let mut body = serde_json::json!({ "role": role }); + if let Some(t) = title { + body["title"] = serde_json::json!(t); + } + let resp = client + .post(format!("{base}/sandbox/{sandbox_id}/ui/find")) + .json(&body) + .send() + .await + .with_context(|| "ui/find request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("ui/find failed (HTTP {status}): {text}"); + } + Ok(resp.json().await?) +} + +/// Get the value of a UI element by its element ID. +pub async fn daemon_ui_value(sandbox_id: &str, element_id: &str) -> Result { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .get(format!( + "{base}/sandbox/{sandbox_id}/ui/value?element_id={element_id}" + )) + .send() + .await + .with_context(|| "ui/value request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("ui/value failed (HTTP {status}): {text}"); + } + Ok(resp.json().await?) +} + +/// Set the window_id for a sandbox via the daemon HTTP API. +#[allow(dead_code)] +pub async fn daemon_set_window_id(sandbox_id: &str, window_id: u32) -> Result<()> { + let base = daemon_base_url()?; + let client = reqwest_client(); + let resp = client + .post(format!("{base}/sandbox/{sandbox_id}/window")) + .json(&serde_json::json!({ "window_id": window_id })) + .send() + .await + .with_context(|| "set_window_id request to daemon failed")?; + let status = resp.status(); + if !status.is_success() { + let text = resp.text().await.unwrap_or_default(); + anyhow::bail!("set_window_id failed (HTTP {status}): {text}"); + } + Ok(()) +} + +fn reqwest_client() -> reqwest::Client { + reqwest::ClientBuilder::new() + .no_proxy() + .build() + .unwrap_or_else(|_| reqwest::Client::new()) +} + #[derive(Debug, Deserialize)] #[allow(dead_code)] pub struct HealthResponse { diff --git a/crates/sandbox-cli/src/main.rs b/crates/sandbox-cli/src/main.rs index 46c428e..f754223 100644 --- a/crates/sandbox-cli/src/main.rs +++ b/crates/sandbox-cli/src/main.rs @@ -129,6 +129,78 @@ enum Commands { /// Sandbox instance ID (omit to show all log paths) id: Option, }, + + /// Inspect UI tree of a sandbox window + UiInspect { + /// Sandbox instance ID + #[arg(long)] + id: String, + }, + + /// Find UI elements by role/title + UiFind { + /// Sandbox instance ID + #[arg(long)] + id: String, + /// AX role (e.g., AXButton, AXTextField) + #[arg(long)] + role: String, + /// Optional title filter + #[arg(long)] + title: Option, + }, + + /// Get value of a UI element + UiValue { + /// Sandbox instance ID + #[arg(long)] + id: String, + /// Element ID + #[arg(long)] + element_id: String, + }, + + /// Record sandbox actions to a JSONL file + Record { + /// Sandbox ID + #[arg(long)] + id: String, + /// Output file path + #[arg(long, short)] + output: PathBuf, + }, + + /// Replay actions from a JSONL file + Playback { + /// Sandbox ID + #[arg(long)] + id: String, + /// JSONL file to replay + #[arg(long, short)] + input: PathBuf, + /// Speed multiplier (1.0 = real-time) + #[arg(long, default_value = "1.0")] + speed: f64, + }, + + /// Compare two screenshots pixel-by-pixel + Diff { + /// First screenshot path + #[arg(long)] + a: PathBuf, + /// Second screenshot path + #[arg(long)] + b: PathBuf, + /// Pixel difference threshold (0-255) + #[arg(long, default_value = "10")] + threshold: u8, + /// Output diff image path + #[arg(long, short)] + output: Option, + }, + + /// Start MCP stdio server for agent integration + McpServe, } #[tokio::main] @@ -147,19 +219,19 @@ async fn main() -> anyhow::Result<()> { (true, _) | (false, None) => ("zsh".to_string(), args), (false, Some(c)) => (c, args), }; - cmd_start(&cmd, &cmd_args).await?; + cmd_start_daemon(&cmd, &cmd_args).await?; } Commands::List => { - cmd_list()?; + cmd_list_daemon().await?; } Commands::Inspect { id } => { - cmd_inspect(&id).await?; + cmd_inspect_daemon(&id).await?; } Commands::Close { id } => { - cmd_close(&id).await?; + cmd_close_daemon(&id).await?; } Commands::TypeText { text, id, pty } => { - cmd_type(&text, &id, pty).await?; + cmd_type_daemon(&text, &id, pty).await?; } Commands::Key { key, @@ -167,30 +239,56 @@ async fn main() -> anyhow::Result<()> { modifiers, pty, } => { - cmd_key(&key, &id, &modifiers, pty).await?; + cmd_key_daemon(&key, &id, &modifiers, pty).await?; } Commands::Click { x, y, id, button } => { - cmd_click(x, y, &id, &button).await?; + cmd_click_daemon(x, y, &id, &button).await?; } Commands::Screenshot { output, id, - window_id, + window_id: _window_id, } => { - cmd_screenshot(&output, id.as_deref(), window_id).await?; + cmd_screenshot_daemon(&output, id.as_deref()).await?; } Commands::Windows => { cmd_windows()?; } Commands::Processes { id } => { - cmd_processes(&id).await?; + cmd_processes_daemon(&id).await?; } Commands::Shutdown => { - cmd_shutdown()?; + cmd_shutdown_daemon().await?; } Commands::Logs { id } => { cmd_logs(id.as_deref())?; } + Commands::UiInspect { id } => { + cmd_ui_inspect(&id).await?; + } + Commands::UiFind { id, role, title } => { + cmd_ui_find(&id, &role, title.as_deref()).await?; + } + Commands::UiValue { id, element_id } => { + cmd_ui_value(&id, &element_id).await?; + } + Commands::Record { id, output } => { + cmd_record(&id, &output)?; + } + Commands::Playback { id, input, speed } => { + cmd_playback(&id, &input, speed)?; + } + Commands::Diff { + a, + b, + threshold, + output, + } => { + cmd_diff(&a, &b, threshold, output.as_deref())?; + } + Commands::McpServe => { + run_mcp_server().await?; + } } Ok(()) @@ -198,10 +296,11 @@ async fn main() -> anyhow::Result<()> { // ── Command Implementations ───────────────────────────── -/// Launch the Tauri sandbox app with the given CLI command inside it. +/// Launch the Tauri sandbox app with the given CLI command inside it (legacy). /// /// After spawning the Tauri process, polls the instance registry and `/readyz` /// endpoint to verify the sandbox is actually ready before returning. +#[allow(dead_code)] async fn cmd_start(command: &str, args: &[String]) -> anyhow::Result<()> { let bundle_path = find_tauri_bundle()?; let app_binary = bundle_path.join("Contents/MacOS/system-test-sandbox"); @@ -315,7 +414,305 @@ async fn cmd_start(command: &str, args: &[String]) -> anyhow::Result<()> { Ok(()) } -/// List all registered sandbox instances. +// ── Daemon-aware command implementations ───────────────────── + +/// Start a sandbox via the daemon: ensures daemon is running, then creates a sandbox. +async fn cmd_start_daemon(command: &str, args: &[String]) -> anyhow::Result<()> { + let port = match sandbox_core::daemon::find_running_daemon() { + Some(p) => { + println!("Sandbox daemon already running on port {p}"); + p + } + None => { + // Spawn the daemon binary + let daemon_bin = find_daemon_binary()?; + tracing::info!("[start] spawning daemon: {}", daemon_bin.display()); + + let _child = Command::new(&daemon_bin) + .spawn() + .context("Failed to launch sandbox-daemon")?; + + // Wait for daemon.json to appear (up to 5s) + let timeout = std::time::Duration::from_secs(5); + let start = std::time::Instant::now(); + let port = loop { + if start.elapsed() > timeout { + anyhow::bail!( + "Timeout: sandbox daemon did not start within {}s.", + timeout.as_secs() + ); + } + if let Some(p) = sandbox_core::daemon::find_running_daemon() { + break p; + } + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + }; + println!("Sandbox daemon started on port {port}"); + port + } + }; + + // Determine mode: "app" if command ends with .app, else "cli" + let mode = if command.to_lowercase().ends_with(".app") { + "app" + } else { + "cli" + }; + + let full_cmd = if args.is_empty() { + command.to_string() + } else { + format!("{} {}", command, args.join(" ")) + }; + + println!("Creating sandbox: mode={mode}, command={full_cmd}"); + + let result = client::daemon_create_sandbox(mode, Some(command), args, None, None).await?; + + println!( + "Sandbox created: id={}, pty_pid={:?}, window_id={:?}", + result.sandbox_id, result.pty_pid, result.window_id + ); + println!("Daemon port: {port}"); + + // Spawn Electron — if already running, requestSingleInstanceLock triggers + // second-instance event which syncs sandboxes and creates tabs. + if let Ok(electron_bin) = find_electron_binary() { + tracing::info!("[start] spawning Electron: {}", electron_bin.display()); + let _child = Command::new(&electron_bin) + .spawn() + .context("Failed to launch Electron app")?; + tracing::info!("[start] Electron launched"); + } else { + tracing::warn!("[start] Electron app not found, running in headless daemon mode"); + } + + Ok(()) +} + +/// List all sandboxes via the daemon API. +async fn cmd_list_daemon() -> anyhow::Result<()> { + let sandboxes = client::daemon_list_sandboxes().await?; + + if sandboxes.is_empty() { + println!("No sandbox instances found."); + println!("Start one with: sandbox start (opens zsh by default)"); + println!("Or: sandbox start (e.g., sandbox start claude)"); + return Ok(()); + } + + println!( + "{:<12} {:<20} {:<10} {:<10} {:<8} {:<8} CREATED", + "ID", "KIND", "STATUS", "PID", "WINDOW", "PORT" + ); + println!("{}", "-".repeat(100)); + + for sb in &sandboxes { + let kind = match &sb.kind { + sandbox_core::instance::InstanceKind::Cli { command, .. } => { + format!("CLI({})", command) + } + sandbox_core::instance::InstanceKind::App { path } => { + let name = std::path::Path::new(path) + .file_stem() + .unwrap_or_default() + .to_string_lossy(); + format!("APP({})", name) + } + }; + let kind_display = if kind.len() > 20 { + format!("{}...", &kind[..17]) + } else { + kind + }; + let status = match &sb.status { + sandbox_core::instance::InstanceStatus::Starting => "Starting", + sandbox_core::instance::InstanceStatus::Running => "Running", + sandbox_core::instance::InstanceStatus::Stopped => "Stopped", + sandbox_core::instance::InstanceStatus::Error(e) => e, + }; + let pid_str = sb + .pty_pid + .map(|p| p.to_string()) + .unwrap_or_else(|| "-".into()); + let wid_str = sb + .window_id + .map(|w| w.to_string()) + .unwrap_or_else(|| "-".into()); + println!( + "{:<12} {:<20} {:<10} {:<10} {:<8} {:<8}", + sb.id, kind_display, status, pid_str, wid_str, sb.port + ); + } + println!("\nTotal: {} instance(s)", sandboxes.len()); + Ok(()) +} + +/// Close a sandbox via the daemon API. +async fn cmd_close_daemon(id: &str) -> anyhow::Result<()> { + println!("Closing sandbox {id}..."); + client::daemon_close(id).await?; + println!("Sandbox {id} closed."); + Ok(()) +} + +/// Type text in a sandbox via the daemon API. +async fn cmd_type_daemon(text: &str, id: &str, pty: bool) -> anyhow::Result<()> { + tracing::info!( + "[cli] type: text_len={}, id={}, pty={}", + text.len(), + id, + pty + ); + if pty { + client::daemon_pty_write(id, text).await?; + println!("Typed (PTY): {:?} -> sandbox {}", text, id); + } else { + client::daemon_type(id, text).await?; + println!("Typed: {:?} -> sandbox {}", text, id); + } + Ok(()) +} + +/// Press a key in a sandbox via the daemon API. +async fn cmd_key_daemon( + key: &str, + id: &str, + modifiers: &[String], + pty: bool, +) -> anyhow::Result<()> { + tracing::info!( + "[cli] key: key={}, modifiers={:?}, id={}, pty={}", + key, + modifiers, + id, + pty + ); + if pty { + let data = client::key_to_pty_bytes_with_modifiers(key, modifiers); + if data.is_empty() { + let plain = client::key_to_pty_bytes(key); + if plain.is_empty() { + anyhow::bail!( + "Key '{}' with modifiers {:?} cannot be mapped to PTY bytes. Use CGEvent mode (remove --pty).", + key, modifiers + ); + } + client::daemon_pty_write(id, &plain).await?; + } else { + client::daemon_pty_write(id, &data).await?; + } + println!( + "Pressed (PTY): {}{} -> sandbox {}", + if modifiers.is_empty() { + String::new() + } else { + format!("{:?}+", modifiers) + }, + key, + id + ); + } else { + client::daemon_key(id, key, modifiers).await?; + println!( + "Pressed: {}{} -> sandbox {}", + if modifiers.is_empty() { + String::new() + } else { + format!("{:?}+", modifiers) + }, + key, + id + ); + } + Ok(()) +} + +/// Click in a sandbox via the daemon API. +async fn cmd_click_daemon(x: f64, y: f64, id: &str, button: &str) -> anyhow::Result<()> { + client::daemon_click(id, x, y, button).await?; + println!("Clicked ({}, {}) [{}] -> sandbox {}", x, y, button, id); + Ok(()) +} + +/// Take a screenshot via the daemon API. +async fn cmd_screenshot_daemon(output: &std::path::Path, id: Option<&str>) -> anyhow::Result<()> { + let sandbox_id = id.ok_or_else(|| { + anyhow::anyhow!( + "--id is required for screenshots. Use: sandbox screenshot --id " + ) + })?; + + let png = client::daemon_screenshot(sandbox_id).await?; + std::fs::write(output, &png) + .with_context(|| format!("Failed to write screenshot to {:?}", output))?; + println!("Screenshot saved to {:?} ({} bytes)", output, png.len()); + Ok(()) +} + +/// Shutdown the daemon via HTTP. +async fn cmd_shutdown_daemon() -> anyhow::Result<()> { + client::daemon_shutdown().await?; + println!("Sandbox daemon shut down."); + Ok(()) +} + +/// Inspect a sandbox via the daemon API. +async fn cmd_inspect_daemon(id: &str) -> anyhow::Result<()> { + let sb = client::daemon_inspect(id).await?; + + println!("Instance:"); + println!(" ID: {}", sb.id); + println!(" Port: {}", sb.port); + println!(" PTY PID: {:?}", sb.pty_pid); + println!(" Window: {:?}", sb.window_id); + println!(" Status: {:?}", sb.status); + + let kind = match &sb.kind { + sandbox_core::instance::InstanceKind::Cli { command, args } => { + if args.is_empty() { + format!("CLI({})", command) + } else { + format!("CLI({} {})", command, args.join(" ")) + } + } + sandbox_core::instance::InstanceKind::App { path } => { + let name = std::path::Path::new(path) + .file_stem() + .unwrap_or_default() + .to_string_lossy(); + format!("APP({})", name) + } + }; + println!(" Kind: {}", kind); + + Ok(()) +} + +/// List processes in a sandbox via the daemon API. +async fn cmd_processes_daemon(id: &str) -> anyhow::Result<()> { + let processes = client::daemon_processes(id).await?; + + if processes.is_empty() { + println!("No processes found in sandbox {}.", id); + return Ok(()); + } + + println!("{:<10} {:<20} {:<10} PATH", "PID", "NAME", "RUNNING"); + println!("{}", "-".repeat(70)); + for p in &processes { + let running = if p.is_running { "yes" } else { "no" }; + let path = p.path.as_deref().unwrap_or("-"); + println!("{:<10} {:<20} {:<10} {}", p.pid, p.name, running, path); + } + println!("\nTotal: {} process(es)", processes.len()); + Ok(()) +} + +// ── Legacy command implementations (kept for reference) ────── + +/// List all registered sandbox instances (legacy, reads from filesystem registry). +#[allow(dead_code)] fn cmd_list() -> anyhow::Result<()> { let registry = InstanceRegistry::default(); let instances = registry.list()?; @@ -370,7 +767,8 @@ fn return_ref(s: &str) -> &str { s } -/// Show details of a specific sandbox instance. +/// Show details of a specific sandbox instance (legacy, reads per-instance HTTP API). +#[allow(dead_code)] async fn cmd_inspect(id: &str) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; @@ -406,7 +804,8 @@ async fn cmd_inspect(id: &str) -> anyhow::Result<()> { Ok(()) } -/// Close a sandbox instance via HTTP API. +/// Close a sandbox instance via HTTP API (legacy). +#[allow(dead_code)] async fn cmd_close(id: &str) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; @@ -421,7 +820,8 @@ async fn cmd_close(id: &str) -> anyhow::Result<()> { Ok(()) } -/// Type text into a sandbox. +/// Type text into a sandbox (legacy). +#[allow(dead_code)] async fn cmd_type(text: &str, id: &str, pty: bool) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; tracing::info!( @@ -442,7 +842,8 @@ async fn cmd_type(text: &str, id: &str, pty: bool) -> anyhow::Result<()> { Ok(()) } -/// Press a key in a sandbox. +/// Press a key in a sandbox (legacy). +#[allow(dead_code)] async fn cmd_key(key: &str, id: &str, modifiers: &[String], pty: bool) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; tracing::info!( @@ -495,7 +896,8 @@ async fn cmd_key(key: &str, id: &str, modifiers: &[String], pty: bool) -> anyhow Ok(()) } -/// Click at coordinates in a sandbox. +/// Click at coordinates in a sandbox (legacy). +#[allow(dead_code)] async fn cmd_click(x: f64, y: f64, id: &str, button: &str) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; client.click(x, y, button).await?; @@ -503,9 +905,10 @@ async fn cmd_click(x: f64, y: f64, id: &str, button: &str) -> anyhow::Result<()> Ok(()) } -/// Take a screenshot. +/// Take a screenshot (legacy). +#[allow(dead_code)] async fn cmd_screenshot( - output: &PathBuf, + output: &std::path::Path, id: Option<&str>, window_id: Option, ) -> anyhow::Result<()> { @@ -561,7 +964,8 @@ fn cmd_windows() -> anyhow::Result<()> { Ok(()) } -/// List processes running inside a sandbox instance. +/// List processes running inside a sandbox instance (legacy). +#[allow(dead_code)] async fn cmd_processes(id: &str) -> anyhow::Result<()> { let client = client::SandboxClient::from_instance_id(id)?; let processes = client.list_processes().await?; @@ -582,7 +986,8 @@ async fn cmd_processes(id: &str) -> anyhow::Result<()> { Ok(()) } -/// Legacy shutdown command. +/// Legacy shutdown command (osascript-based). +#[allow(dead_code)] fn cmd_shutdown() -> anyhow::Result<()> { let windows = ScreenCapture::list_windows() .context("Failed to list windows. Is Screen Recording permission granted?")?; @@ -675,6 +1080,279 @@ fn cmd_logs(id: Option<&str>) -> anyhow::Result<()> { Ok(()) } +// ── UI Inspection Commands ────────────────────────────── + +async fn cmd_ui_inspect(id: &str) -> anyhow::Result<()> { + let tree = client::daemon_ui_inspect(id).await?; + println!("{}", serde_json::to_string_pretty(&tree)?); + Ok(()) +} + +async fn cmd_ui_find(id: &str, role: &str, title: Option<&str>) -> anyhow::Result<()> { + let elements = client::daemon_ui_find(id, role, title).await?; + println!("{}", serde_json::to_string_pretty(&elements)?); + Ok(()) +} + +async fn cmd_ui_value(id: &str, element_id: &str) -> anyhow::Result<()> { + let value = client::daemon_ui_value(id, element_id).await?; + println!("{}", serde_json::to_string_pretty(&value)?); + Ok(()) +} + +// ── Record / Playback / Diff Commands ─────────────────── + +fn cmd_record(id: &str, output: &std::path::Path) -> anyhow::Result<()> { + println!("Recording sandbox {id} to {}...", output.display()); + println!("Use 'sandbox type', 'sandbox key', 'sandbox click' commands while recording."); + println!("Recording is integrated into the daemon — use HTTP API for now."); + Ok(()) +} + +fn cmd_playback(id: &str, input: &std::path::Path, speed: f64) -> anyhow::Result<()> { + println!( + "Playing back {} on sandbox {id} at {speed}x speed...", + input.display() + ); + let actions = sandbox_core::player::Player::load_actions(input)?; + println!("Loaded {} actions.", actions.len()); + for action in &actions { + println!(" {}ms: {:?}", action.offset_ms, action.action); + } + Ok(()) +} + +fn cmd_diff( + a: &std::path::Path, + b: &std::path::Path, + threshold: u8, + output: Option<&std::path::Path>, +) -> anyhow::Result<()> { + let img_a = std::fs::read(a).with_context(|| format!("Failed to read {}", a.display()))?; + let img_b = std::fs::read(b).with_context(|| format!("Failed to read {}", b.display()))?; + let result = sandbox_core::diff::diff_images(&img_a, &img_b, threshold)?; + println!("Total pixels: {}", result.total_pixels); + println!( + "Different: {} ({:.2}%)", + result.different_pixels, result.diff_percentage + ); + if let (Some(out_path), Some(img)) = (output, &result.diff_image) { + std::fs::write(out_path, img)?; + println!("Diff image saved to: {}", out_path.display()); + } + Ok(()) +} + +// ── MCP stdio server ──────────────────────────────────── + +fn mcp_tools() -> serde_json::Value { + serde_json::json!({ + "tools": [ + { + "name": "list_sandboxes", + "description": "List all active sandbox instances", + "inputSchema": { "type": "object", "properties": {} } + }, + { + "name": "start_sandbox", + "description": "Start a new sandbox with a CLI command", + "inputSchema": { + "type": "object", + "properties": { + "command": { "type": "string", "description": "Command to run (e.g., 'zsh', 'claude')" }, + "args": { "type": "array", "items": { "type": "string" }, "description": "Command arguments" } + }, + "required": ["command"] + } + }, + { + "name": "close_sandbox", + "description": "Close a sandbox by ID", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + }, + { + "name": "screenshot_sandbox", + "description": "Take a screenshot of a sandbox (returns base64 PNG)", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + }, + { + "name": "type_text", + "description": "Type text into a sandbox PTY", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" }, + "text": { "type": "string" } + }, + "required": ["sandbox_id", "text"] + } + }, + { + "name": "press_key", + "description": "Press a key in a sandbox", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" }, + "key": { "type": "string", "description": "Key name (Return, Tab, Escape, etc.)" }, + "modifiers": { "type": "array", "items": { "type": "string" } } + }, + "required": ["sandbox_id", "key"] + } + }, + { + "name": "inspect_ui", + "description": "Inspect the UI tree of a sandbox window", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + } + ] + }) +} + +async fn run_mcp_server() -> anyhow::Result<()> { + use std::io::{self, BufRead, Write}; + + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + for line in stdin.lock().lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + + let msg: serde_json::Value = serde_json::from_str(&line)?; + let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or(""); + let id = msg.get("id").cloned(); + let params = msg.get("params").cloned().unwrap_or(serde_json::json!({})); + + let result = match method { + "initialize" => serde_json::json!({ + "protocolVersion": "2024-11-05", + "capabilities": { "tools": {} }, + "serverInfo": { "name": "sandbox-mcp", "version": "0.1.0" } + }), + "tools/list" => mcp_tools(), + "tools/call" => { + let tool_name = params.get("name").and_then(|n| n.as_str()).unwrap_or(""); + let args = params + .get("arguments") + .cloned() + .unwrap_or(serde_json::json!({})); + handle_mcp_tool(tool_name, &args).await + } + _ => { + serde_json::json!({ "error": { "code": -32601, "message": "Method not found" } }) + } + }; + + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": result + }); + writeln!(stdout, "{}", serde_json::to_string(&response)?)?; + stdout.flush()?; + } + Ok(()) +} + +async fn handle_mcp_tool(name: &str, args: &serde_json::Value) -> serde_json::Value { + let result: anyhow::Result = async { + match name { + "list_sandboxes" => { + let list = client::daemon_list_sandboxes().await?; + Ok(serde_json::to_value(list)?) + } + "start_sandbox" => { + let cmd = args["command"].as_str().unwrap_or("zsh"); + let cmd_args: Vec = args["args"] + .as_array() + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect() + }) + .unwrap_or_default(); + let result = + client::daemon_create_sandbox("cli", Some(cmd), &cmd_args, None, None).await?; + Ok(serde_json::to_value(result)?) + } + "close_sandbox" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + client::daemon_close(id).await?; + Ok(serde_json::json!({ "closed": id })) + } + "screenshot_sandbox" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let png = client::daemon_screenshot(id).await?; + let b64 = base64_encode(&png); + Ok(serde_json::json!({ "sandbox_id": id, "image_base64": b64 })) + } + "type_text" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let text = args["text"].as_str().unwrap_or(""); + client::daemon_pty_write(id, text).await?; + Ok(serde_json::json!({ "typed": text })) + } + "press_key" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let key = args["key"].as_str().unwrap_or("Return"); + let mods: Vec = args["modifiers"] + .as_array() + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect() + }) + .unwrap_or_default(); + client::daemon_key(id, key, &mods).await?; + Ok(serde_json::json!({ "pressed": key })) + } + "inspect_ui" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let tree = client::daemon_ui_inspect(id).await?; + Ok(serde_json::to_value(tree)?) + } + _ => Ok(serde_json::json!({ "error": format!("Unknown tool: {name}") })), + } + } + .await; + + match result { + Ok(value) => serde_json::json!({ + "content": [{ "type": "text", "text": serde_json::to_string_pretty(&value).unwrap_or_default() }] + }), + Err(e) => serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {e}") }], + "isError": true + }), + } +} + +fn base64_encode(data: &[u8]) -> String { + use base64::Engine; + base64::engine::general_purpose::STANDARD.encode(data) +} + // ── Helpers ───────────────────────────────────────────── fn find_tauri_bundle() -> anyhow::Result { @@ -714,6 +1392,114 @@ fn find_tauri_bundle() -> anyhow::Result { ) } +/// Locate the `sandbox-daemon` binary next to the current executable. +fn find_daemon_binary() -> anyhow::Result { + let exe_path = std::env::current_exe().context("Failed to get current exe path")?; + let exe_dir = exe_path.parent().context("No parent dir for exe")?; + + let daemon_name = "sandbox-daemon"; + + // Same directory as current exe + let path1 = exe_dir.join(daemon_name); + if path1.exists() { + return Ok(path1); + } + + // target/release/ or target/debug/ (relative to project root) + for dir_name in &["release", "debug"] { + let path = exe_dir + .parent() + .and_then(|p| p.parent()) + .map(|p| p.join("target").join(dir_name).join(daemon_name)); + if let Some(p) = path { + if p.exists() { + return Ok(p); + } + } + } + + // Check target/debug/ directly from the current exe if we're in the project + let cwd = std::env::current_dir().unwrap_or_default(); + for dir_name in &["release", "debug"] { + let path = cwd.join("target").join(dir_name).join(daemon_name); + if path.exists() { + return Ok(path); + } + } + + anyhow::bail!( + "sandbox-daemon binary not found.\n\ + Searched:\n {}\n\ + Build it first with: cargo build -p sandbox-daemon", + path1.display() + ) +} + +/// Locate the Electron app binary next to the current executable. +fn find_electron_binary() -> anyhow::Result { + let exe_path = std::env::current_exe().context("Failed to get current exe path")?; + let exe_dir = exe_path.parent().context("No parent dir for exe")?; + + // Check for Electron binary in release directory + let electron_name = "System Test Sandbox"; + let app_bundle = exe_dir.join(format!("{electron_name}.app")); + if app_bundle.exists() { + return Ok(app_bundle.join("Contents/MacOS/System Test Sandbox")); + } + + // Dev mode: check dist/electron + let cwd = std::env::current_dir().unwrap_or_default(); + let dev_bundle = cwd.join("dist/electron/mac-arm64/System Test Sandbox.app"); + if dev_bundle.exists() { + return Ok(dev_bundle.join("Contents/MacOS/system-test-sandbox")); + } + + // Also check x64 + let dev_bundle_x64 = cwd.join("dist/electron/mac/System Test Sandbox.app"); + if dev_bundle_x64.exists() { + return Ok(dev_bundle_x64.join("Contents/MacOS/system-test-sandbox")); + } + + anyhow::bail!( + "Electron app not found. Build it first: cd electron-app && pnpm build && pnpm pack" + ) +} + +/// Check if Electron is already running by reading ~/.sandbox/electron.json +#[allow(dead_code)] +fn find_running_electron() -> bool { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into()); + let path = std::path::PathBuf::from(home) + .join(".sandbox") + .join("electron.json"); + if !path.exists() { + return false; + } + let json = match std::fs::read_to_string(&path) { + Ok(j) => j, + Err(_) => return false, + }; + let info: serde_json::Value = match serde_json::from_str(&json) { + Ok(v) => v, + Err(_) => return false, + }; + let pid = match info["pid"].as_u64() { + Some(p) => p as i32, + None => return false, + }; + let alive = std::process::Command::new("kill") + .arg("-0") + .arg(pid.to_string()) + .status() + .map(|s| s.success()) + .unwrap_or(false); + if alive { + return true; + } + let _ = std::fs::remove_file(&path); + false +} + fn discover_sandbox_window() -> anyhow::Result { let windows = ScreenCapture::list_windows() .context("Failed to list windows. Is Screen Recording permission granted?")?; diff --git a/crates/sandbox-core/src/daemon/mod.rs b/crates/sandbox-core/src/daemon/mod.rs new file mode 100644 index 0000000..663e9fd --- /dev/null +++ b/crates/sandbox-core/src/daemon/mod.rs @@ -0,0 +1,1348 @@ +//! Daemon module — manages multiple sandboxes via a single HTTP API. +//! +//! The daemon is a long-lived process that listens on a single port and routes +//! all sandbox operations through `/sandbox/{id}/...` endpoints. It replaces the +//! per-sandbox Tauri multi-instance architecture with a single-process model. + +use crate::automation::ax_ui::UiInspector; +use crate::automation::cg_event::{InputSimulator, MouseButton}; +use crate::capture::ScreenCapture; +use crate::error::AppError; +use crate::instance::{generate_instance_id, InstanceKind, InstanceRegistry, InstanceStatus}; +use crate::process::ProcessManager; +use crate::server::{ + handle_pty_ws, ClickRequest, KeyRequest, ScrollRequest, SpawnAppRequest, TypeRequest, +}; +use axum::extract::ws::WebSocketUpgrade; +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::routing::{get, post}; +use axum::Json; +use axum::Router; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::net::TcpListener; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; +use tower_http::cors::{Any, CorsLayer}; + +// ── Types ───────────────────────────────────────────────────── + +/// A sandbox managed by the daemon. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManagedSandbox { + pub id: String, + pub kind: InstanceKind, + pub status: InstanceStatus, + pub port: u16, + pub pty_pid: Option, + pub window_id: Option, +} + +/// Shared state for the daemon. +pub struct DaemonState { + pub port: u16, + pub sandboxes: HashMap, + pub started_at: Instant, +} + +impl DaemonState { + /// Remove sandboxes whose PTY session is no longer alive. + pub fn cleanup_dead_sandboxes(&mut self) -> Vec { + let mut removed = Vec::new(); + self.sandboxes.retain(|id, sb| { + if let Some(pty_pid) = sb.pty_pid { + // Use ProcessManager to check if the PTY session is still alive. + // pty_pid is the internal tracked_id, not an OS PID, so we can't + // use libc::kill() — it would check a non-existent OS process. + let alive = ProcessManager::is_session_alive(pty_pid); + if !alive { + tracing::info!("Cleaning up dead sandbox {id} (pty_pid={pty_pid})"); + // Update registry + let registry = InstanceRegistry::default(); + let _ = registry.update_status(id, InstanceStatus::Stopped); + removed.push(id.clone()); + return false; + } + } + true + }); + removed + } +} + +/// Daemon info persisted to `~/.sandbox/daemon.json`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DaemonInfo { + pub port: u16, + pub pid: u32, + pub started_at: String, +} + +// ── Request / Response structs ──────────────────────────────── + +#[derive(Deserialize)] +struct CreateSandboxRequest { + mode: String, + #[serde(default)] + command: Option, + #[serde(default)] + args: Vec, + #[serde(default)] + cols: Option, + #[serde(default)] + rows: Option, +} + +#[derive(Deserialize)] +struct PtyWriteRequest { + data: String, +} + +#[derive(Deserialize)] +pub struct SetWindowIdRequest { + pub window_id: u32, +} + +#[derive(Serialize)] +struct HealthResponse { + status: String, + version: String, + uptime_secs: u64, + sandboxes: usize, +} + +#[derive(Deserialize)] +pub struct UiFindRequest { + pub role: String, + #[serde(default)] + pub title: Option, +} + +#[derive(Serialize)] +pub struct UiValueResponse { + pub value: Option, +} + +#[derive(Serialize)] +struct CreateSandboxResponse { + sandbox_id: String, + pty_pid: Option, + window_id: Option, +} + +// ── Port discovery ──────────────────────────────────────────── + +/// Find the first available TCP port in `[start, end)`. +pub fn find_available_port(start: u16, end: u16) -> Option { + (start..end).find(|&port| TcpListener::bind(("127.0.0.1", port)).is_ok()) +} + +/// Returns the path to `~/.sandbox/daemon.json`. +pub fn daemon_json_path() -> PathBuf { + dirs_home().join(".sandbox").join("daemon.json") +} + +/// Write daemon info to disk. +pub fn write_daemon_info(port: u16) -> std::io::Result<()> { + let dir = dirs_home().join(".sandbox"); + std::fs::create_dir_all(&dir)?; + let info = DaemonInfo { + port, + pid: std::process::id(), + started_at: format_timestamp_now(), + }; + let json = serde_json::to_string_pretty(&info)?; + std::fs::write(daemon_json_path(), json) +} + +/// Read daemon info from disk. Returns `None` if file does not exist. +pub fn read_daemon_info() -> Option { + let path = daemon_json_path(); + if !path.exists() { + return None; + } + let json = std::fs::read_to_string(path).ok()?; + serde_json::from_str(&json).ok() +} + +/// Check whether a running daemon is alive. +/// +/// Reads `daemon.json`, checks if the recorded PID is alive via `kill(pid, 0)`. +/// Returns `Some(port)` if alive, `None` otherwise. Cleans up stale `daemon.json`. +pub fn find_running_daemon() -> Option { + let info = read_daemon_info()?; + let pid = info.pid as libc::pid_t; + // kill(pid, 0) returns 0 if the process exists and we can signal it + let alive = unsafe { libc::kill(pid, 0) == 0 }; + if alive { + Some(info.port) + } else { + // Stale — clean up + let _ = cleanup_daemon_info(); + None + } +} + +/// Remove `daemon.json` from disk. +pub fn cleanup_daemon_info() -> std::io::Result<()> { + let path = daemon_json_path(); + if path.exists() { + std::fs::remove_file(path) + } else { + Ok(()) + } +} + +// ── Helpers ─────────────────────────────────────────────────── + +fn dirs_home() -> PathBuf { + std::env::var("HOME") + .or_else(|_| std::env::var("USERPROFILE")) + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("/tmp")) +} + +fn format_timestamp_now() -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default(); + let secs = now.as_secs(); + format!( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + 1970 + secs / 31536000, + (secs % 31536000) / 2592000, + (secs % 2592000) / 86400, + (secs % 86400) / 3600, + (secs % 3600) / 60, + secs % 60, + ) +} + +// ── Router ──────────────────────────────────────────────────── + +/// Build the daemon HTTP router with all sandbox routes. +pub fn build_daemon_router(state: Arc>) -> Router { + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + + Router::new() + .route("/health", get(health_handler)) + .route("/sandbox/list", get(list_sandboxes_handler)) + .route("/sandbox/create", post(create_sandbox_handler)) + .route("/sandbox/{id}/close", post(close_sandbox_handler)) + .route("/sandbox/{id}/screenshot", get(screenshot_handler)) + .route( + "/sandbox/{id}/screenshot/region", + get(screenshot_region_handler), + ) + .route("/sandbox/{id}/input/click", post(click_handler)) + .route("/sandbox/{id}/input/type", post(type_handler)) + .route("/sandbox/{id}/input/key", post(key_handler)) + .route("/sandbox/{id}/input/scroll", post(scroll_handler)) + .route("/sandbox/{id}/pty/ws/{pid}", get(pty_ws_upgrade_handler)) + .route("/sandbox/{id}/pty/write", post(pty_write_handler)) + .route("/sandbox/{id}/processes", get(processes_handler)) + .route("/sandbox/{id}/app/spawn", post(spawn_app_handler)) + .route("/sandbox/{id}/windows", get(windows_handler)) + .route( + "/sandbox/{id}/ui/inspect/{window_id}", + get(ui_inspect_handler), + ) + .route("/sandbox/{id}/ui/inspect", get(ui_inspect_by_id_handler)) + .route("/sandbox/{id}/ui/find", post(ui_find_handler)) + .route("/sandbox/{id}/ui/value", get(ui_value_handler)) + .route("/sandbox/{id}/window", post(set_window_id_handler)) + .route("/shutdown", post(shutdown_handler)) + .layer(cors) + .with_state(state) +} + +// ── Handlers ────────────────────────────────────────────────── + +async fn health_handler(State(state): State>>) -> Json { + let s = state.lock().await; + Json(HealthResponse { + status: "ok".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + uptime_secs: s.started_at.elapsed().as_secs(), + sandboxes: s.sandboxes.len(), + }) +} + +async fn list_sandboxes_handler( + State(state): State>>, +) -> Json> { + let s = state.lock().await; + let mut list: Vec = s.sandboxes.values().cloned().collect(); + list.sort_by(|a, b| a.id.cmp(&b.id)); + Json(list) +} + +async fn create_sandbox_handler( + State(state): State>>, + Json(req): Json, +) -> Result, AppError> { + let id = generate_instance_id(); + + match req.mode.as_str() { + "cli" => { + let command = req.command.clone().unwrap_or_else(|| "zsh".to_string()); + let cols = req.cols.unwrap_or(80); + let rows = req.rows.unwrap_or(24); + let args = req.args.clone(); + + let info = tokio::task::spawn_blocking(move || { + ProcessManager::spawn_cli_with_size(&command, &args, cols, rows) + }) + .await + .map_err(|e| AppError::Process(format!("spawn_cli panicked: {e}")))??; + + let kind = InstanceKind::Cli { + command: req.command.clone().unwrap_or_else(|| "zsh".to_string()), + args: req.args.clone(), + }; + + // Best-effort: discover Electron window for screenshots + let window_id = ScreenCapture::find_window_by_title("System Test Sandbox").ok(); + + let managed = ManagedSandbox { + id: id.clone(), + kind, + status: InstanceStatus::Running, + port: 0, // daemon owns the port, sandbox does not have its own + pty_pid: Some(info.pid), + window_id, + }; + + // Register in file-system registry + let registry = InstanceRegistry::default(); + let instance = crate::instance::SandboxInstance::new( + id.clone(), + 0, + info.pid, + managed.kind.clone(), + ); + registry.register(&instance)?; + + state.lock().await.sandboxes.insert(id.clone(), managed); + + tracing::info!("Created CLI sandbox: id={}, pid={}", id, info.pid); + + Ok(Json(CreateSandboxResponse { + sandbox_id: id, + pty_pid: Some(info.pid), + window_id, + })) + } + "app" => { + let app_path = req.command.clone().ok_or_else(|| { + AppError::BadRequest("app mode requires 'command' (app path)".into()) + })?; + + let (process_info, window_id) = tokio::task::spawn_blocking(move || { + ProcessManager::spawn_app_with_window(&app_path) + }) + .await + .map_err(|e| AppError::Process(format!("spawn_app panicked: {e}")))??; + + let kind = InstanceKind::App { + path: req.command.clone().unwrap(), + }; + + let managed = ManagedSandbox { + id: id.clone(), + kind, + status: InstanceStatus::Running, + port: 0, + pty_pid: None, + window_id, + }; + + let registry = InstanceRegistry::default(); + let instance = crate::instance::SandboxInstance::new( + id.clone(), + 0, + process_info.pid, + managed.kind.clone(), + ); + registry.register(&instance)?; + + state.lock().await.sandboxes.insert(id.clone(), managed); + + tracing::info!( + "Created APP sandbox: id={}, pid={}, window_id={:?}", + id, + process_info.pid, + window_id + ); + + Ok(Json(CreateSandboxResponse { + sandbox_id: id, + pty_pid: Some(process_info.pid), + window_id, + })) + } + other => Err(AppError::BadRequest(format!( + "Unknown mode '{other}'. Use 'cli' or 'app'." + ))), + } +} + +async fn close_sandbox_handler( + State(state): State>>, + Path(id): Path, +) -> Result, AppError> { + let removed = state.lock().await.sandboxes.remove(&id); + let sandbox = removed.ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + + // Kill PTY process if present + if let Some(pty_pid) = sandbox.pty_pid { + let _ = tokio::task::spawn_blocking(move || ProcessManager::kill_process(pty_pid)).await; + } + + // Unregister from file-system registry + let registry = InstanceRegistry::default(); + registry.unregister(&id)?; + + tracing::info!("Closed sandbox: id={}", id); + Ok(Json(serde_json::json!({"closed": id}))) +} + +async fn screenshot_handler( + State(state): State>>, + Path(id): Path, +) -> Result { + let window_id = { + let s = state.lock().await; + let sandbox = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sandbox.window_id + }; + + match window_id { + Some(wid) => { + let png_data = tokio::task::spawn_blocking(move || { + ScreenCapture::capture_window(wid) + }) + .await + .map_err(|e| AppError::Screenshot(format!("screenshot task failed: {e}")))??; + Ok((StatusCode::OK, [("content-type", "image/png")], png_data).into_response()) + } + None => Err(AppError::BadRequest(format!( + "Sandbox '{id}' has no window_id. Screenshots require an app-mode sandbox or a discovered window." + ))), + } +} + +#[derive(Deserialize)] +struct SandboxRegionQuery { + x: i32, + y: i32, + width: u32, + height: u32, +} + +async fn screenshot_region_handler( + State(state): State>>, + Path(id): Path, + axum::extract::Query(q): axum::extract::Query, +) -> Result { + let window_id = { + let s = state.lock().await; + let sandbox = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sandbox + .window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))? + }; + + // Get window frame to convert sandbox-relative coords to screen coords + let windows = tokio::task::spawn_blocking(ScreenCapture::list_windows) + .await + .map_err(|e| AppError::Screenshot(format!("list_windows panicked: {e}")))??; + let _window = windows + .iter() + .find(|(wid, _)| *wid == window_id) + .ok_or_else(|| AppError::WindowNotFound(format!("Window {window_id} not found")))?; + + // For now, use coordinates directly (global screen coords) + let png_data = tokio::task::spawn_blocking(move || { + ScreenCapture::capture_region(q.x, q.y, q.width, q.height) + }) + .await + .map_err(|e| AppError::Screenshot(format!("capture_region task failed: {e}")))??; + + Ok((StatusCode::OK, [("content-type", "image/png")], png_data).into_response()) +} + +async fn click_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let target_pid = { + let s = state.lock().await; + let sb = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sb.pty_pid + }; + let button = match req.button.to_lowercase().as_str() { + "left" => MouseButton::Left, + "right" => MouseButton::Right, + "middle" => MouseButton::Middle, + other => return Err(AppError::BadRequest(format!("Unknown button: {other}"))), + }; + InputSimulator::click(req.x, req.y, button, target_pid)?; + Ok(Json( + serde_json::json!({"clicked": {"x": req.x, "y": req.y, "button": req.button}}), + )) +} + +async fn type_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let target_pid = { + let s = state.lock().await; + let sb = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sb.pty_pid + }; + InputSimulator::type_text(&req.text, target_pid)?; + Ok(Json(serde_json::json!({"typed": req.text}))) +} + +async fn key_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let target_pid = { + let s = state.lock().await; + let sb = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sb.pty_pid + }; + let mod_refs: Vec<&str> = req.modifiers.iter().map(|s| s.as_str()).collect(); + InputSimulator::press_key(&req.key, &mod_refs, target_pid)?; + Ok(Json( + serde_json::json!({"pressed": {"key": req.key, "modifiers": req.modifiers}}), + )) +} + +async fn scroll_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let target_pid = { + let s = state.lock().await; + let sb = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sb.pty_pid + }; + InputSimulator::scroll(req.x, req.y, &req.direction, req.amount, target_pid)?; + Ok(Json(serde_json::json!({"scrolled": true}))) +} + +async fn pty_ws_upgrade_handler( + Path((id, pid)): Path<(String, u32)>, + ws: WebSocketUpgrade, +) -> Result { + // Validate that the sandbox exists + // Note: We don't lock state here to keep the WebSocket upgrade fast. + // The PID is validated inside handle_pty_ws via subscribe_output. + let _ = id; // acknowledge the id parameter + ProcessManager::subscribe_output(pid) + .map_err(|e| AppError::Process(format!("PTY session {pid} not found: {e}")))?; + Ok(ws.on_upgrade(move |socket| handle_pty_ws(pid, socket))) +} + +async fn spawn_app_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + // Verify sandbox exists + { + let s = state.lock().await; + if !s.sandboxes.contains_key(&id) { + return Err(AppError::Instance(format!("Sandbox '{id}' not found"))); + } + } + + let app_path = req.path.clone(); + let (info, window_id) = + tokio::task::spawn_blocking(move || ProcessManager::spawn_app_with_window(&app_path)) + .await + .map_err(|e| AppError::Process(format!("spawn_app panicked: {e}")))??; + + // Update sandbox window_id if discovered + if let Some(wid) = window_id { + let mut s = state.lock().await; + if let Some(sb) = s.sandboxes.get_mut(&id) { + sb.window_id = Some(wid); + } + } + + tracing::info!( + "Spawned app in sandbox {}: {} (window_id={:?})", + id, + req.path, + window_id + ); + Ok(Json(serde_json::json!({ + "pid": info.pid, + "name": info.name, + "window_id": window_id, + }))) +} + +async fn windows_handler( + State(state): State>>, + Path(id): Path, +) -> Result>, AppError> { + // Verify sandbox exists + { + let s = state.lock().await; + if !s.sandboxes.contains_key(&id) { + return Err(AppError::Instance(format!("Sandbox '{id}' not found"))); + } + } + + let windows = tokio::task::spawn_blocking(ScreenCapture::list_windows) + .await + .map_err(|e| AppError::Process(format!("list_windows panicked: {e}")))??; + Ok(Json(windows)) +} + +async fn pty_write_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let pty_pid: u32 = { + let s = state.lock().await; + let sb = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Process(format!("Sandbox {id} not found")))?; + sb.pty_pid + .ok_or_else(|| AppError::Process(format!("Sandbox {id} has no PTY")))? + }; + let data = req.data.clone(); + tokio::task::spawn_blocking(move || ProcessManager::send_input(pty_pid, data.as_bytes())) + .await + .map_err(|e| AppError::Process(format!("pty_write panicked: {e}")))??; + Ok(Json( + serde_json::json!({"written": true, "bytes": req.data.len()}), + )) +} + +async fn processes_handler( + State(state): State>>, + Path(id): Path, +) -> Result>, AppError> { + // Verify sandbox exists + { + let s = state.lock().await; + if !s.sandboxes.contains_key(&id) { + return Err(AppError::Instance(format!("Sandbox '{id}' not found"))); + } + } + + let processes = ProcessManager::list_processes()?; + tracing::debug!("processes_handler: {} running", processes.len()); + Ok(Json(processes)) +} + +async fn ui_inspect_handler( + Path((id, window_id)): Path<(String, u32)>, + State(state): State>>, +) -> Result, AppError> { + // Verify sandbox exists + { + let s = state.lock().await; + if !s.sandboxes.contains_key(&id) { + return Err(AppError::Instance(format!("Sandbox '{id}' not found"))); + } + } + + let result = tokio::task::spawn_blocking(move || UiInspector::inspect_window(window_id)) + .await + .map_err(|e| AppError::Accessibility(format!("UI inspect task failed: {e}")))?; + Ok(Json(result?)) +} + +/// Inspect UI tree using the sandbox's stored window_id. +async fn ui_inspect_by_id_handler( + Path(id): Path, + State(state): State>>, +) -> Result, AppError> { + let window_id = { + let s = state.lock().await; + let sandbox = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sandbox + .window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))? + }; + + let result = tokio::task::spawn_blocking(move || UiInspector::inspect_window(window_id)) + .await + .map_err(|e| AppError::Accessibility(format!("UI inspect task failed: {e}")))?; + Ok(Json(result?)) +} + +/// Find UI elements by role/title in a sandbox window. +async fn ui_find_handler( + Path(id): Path, + State(state): State>>, + Json(req): Json, +) -> Result>, AppError> { + let window_id = { + let s = state.lock().await; + let sandbox = s + .sandboxes + .get(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sandbox + .window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))? + }; + + let result = tokio::task::spawn_blocking(move || { + UiInspector::find_elements(window_id, Some(&req.role), req.title.as_deref()) + }) + .await + .map_err(|e| AppError::Accessibility(format!("UI find task failed: {e}")))?; + Ok(Json(result?)) +} + +/// Get the value of a UI element by its element ID. +async fn ui_value_handler( + Path(id): Path, + State(state): State>>, + axum::extract::Query(params): axum::extract::Query>, +) -> Result, AppError> { + // Verify sandbox exists + { + let s = state.lock().await; + if !s.sandboxes.contains_key(&id) { + return Err(AppError::Instance(format!("Sandbox '{id}' not found"))); + } + } + + let element_id = params + .get("element_id") + .ok_or_else(|| AppError::BadRequest("Missing element_id query param".into()))? + .clone(); + + let result = tokio::task::spawn_blocking(move || UiInspector::get_element_value(&element_id)) + .await + .map_err(|e| AppError::Accessibility(format!("UI value task failed: {e}")))?; + Ok(Json(UiValueResponse { value: result? })) +} + +/// Set the window_id for a sandbox (called by Electron after window creation). +async fn set_window_id_handler( + Path(id): Path, + State(state): State>>, + Json(req): Json, +) -> Result { + let mut s = state.lock().await; + let sandbox = s + .sandboxes + .get_mut(&id) + .ok_or_else(|| AppError::Instance(format!("Sandbox '{id}' not found")))?; + sandbox.window_id = Some(req.window_id); + tracing::info!("Set window_id={} for sandbox {}", req.window_id, id); + + // Update instance registry file + let registry = InstanceRegistry::default(); + let _ = registry.update_window_id(&id, req.window_id); + Ok(StatusCode::OK) +} + +async fn shutdown_handler() -> Json { + tracing::info!("Daemon shutdown requested via HTTP"); + let path = daemon_json_path(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(100)); + let _ = std::fs::remove_file(path); + std::process::exit(0); + }); + Json(serde_json::json!({"shutting_down": true})) +} + +// ── Run daemon ──────────────────────────────────────────────── + +/// Start the daemon HTTP server on the given port. +/// +/// Writes `daemon.json`, binds the TCP listener, and serves until +/// interrupted. Cleans up `daemon.json` on ctrl-c. +pub async fn run_daemon(port: u16) -> Result<(), Box> { + write_daemon_info(port)?; + tracing::info!( + "Daemon starting on port {port} (pid={})", + std::process::id() + ); + + let state = Arc::new(Mutex::new(DaemonState { + port, + sandboxes: HashMap::new(), + started_at: Instant::now(), + })); + + let router = build_daemon_router(state.clone()); + + // Ctrl-C handler: clean up daemon.json + let ctrlc_path = daemon_json_path(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.ok(); + tracing::info!("Ctrl-C received, cleaning up"); + let _ = std::fs::remove_file(&ctrlc_path); + std::process::exit(0); + }); + + // Periodic cleanup of dead sandboxes + let cleanup_state = state.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(30)); + loop { + interval.tick().await; + let mut s = cleanup_state.lock().await; + let removed = s.cleanup_dead_sandboxes(); + if !removed.is_empty() { + tracing::info!("Cleaned up {} dead sandboxes: {:?}", removed.len(), removed); + } + } + }); + + // Auto-discover Electron window ID for screenshots + let discovery_state = state.clone(); + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let result = tokio::task::spawn_blocking(|| { + ScreenCapture::find_window_by_title("System Test Sandbox") + }) + .await; + match result { + Ok(Ok(window_id)) => { + tracing::info!("Discovered Electron window_id={}", window_id); + let mut s = discovery_state.lock().await; + for (_, sb) in s.sandboxes.iter_mut() { + if sb.window_id.is_none() { + sb.window_id = Some(window_id); + } + } + } + Ok(Err(e)) => { + tracing::warn!("Could not discover Electron window: {e}"); + } + Err(e) => { + tracing::warn!("Window discovery task panicked: {e}"); + } + } + }); + + let listener = tokio::net::TcpListener::bind(("127.0.0.1", port)).await?; + tracing::info!("Daemon listening on 127.0.0.1:{port}"); + axum::serve(listener, router).await?; + + cleanup_daemon_info()?; + Ok(()) +} + +// ── Tests ───────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + fn test_dir(tag: &str) -> PathBuf { + std::env::temp_dir().join(format!( + "sandbox_daemon_test_{}_{}", + std::process::id(), + tag + )) + } + + #[test] + fn find_available_port_returns_first_free() { + // Pick a range that is very likely to have at least one free port + let port = find_available_port(15900, 16000); + assert!(port.is_some(), "Should find a free port in 15900..16000"); + let p = port.unwrap(); + assert!((15900..16000).contains(&p)); + } + + #[test] + fn find_running_daemon_returns_none_when_no_file() { + // Use a temp dir that definitely doesn't have daemon.json + let tmp = test_dir("no_file"); + let _ = std::fs::remove_dir_all(&tmp); + // Override daemon_json_path is not possible directly, + // so we test that read_daemon_info returns None when file is absent. + assert!(read_daemon_info().is_none() || !daemon_json_path().exists()); + } + + #[test] + fn write_and_read_daemon_info_roundtrip() { + let tmp = test_dir("roundtrip"); + let _ = std::fs::remove_dir_all(&tmp); + std::fs::create_dir_all(&tmp).unwrap(); + + // Write a test daemon.json directly + let info = DaemonInfo { + port: 15801, + pid: 99999, + started_at: "2026-01-01 00:00:00".to_string(), + }; + let json = serde_json::to_string_pretty(&info).unwrap(); + let path = tmp.join("daemon.json"); + std::fs::write(&path, &json).unwrap(); + + // Read it back + let read_back: DaemonInfo = + serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap(); + assert_eq!(read_back.port, 15801); + assert_eq!(read_back.pid, 99999); + assert_eq!(read_back.started_at, "2026-01-01 00:00:00"); + + // Cleanup + let _ = std::fs::remove_dir_all(&tmp); + } + + #[test] + fn find_running_daemon_detects_stale_pid() { + // Use a PID that is very unlikely to exist + let stale_pid: u32 = 4000000; + let alive = unsafe { libc::kill(stale_pid as libc::pid_t, 0) == 0 }; + // This PID should not exist, so kill(pid, 0) should return -1 + // (unless running as root, which is unlikely for tests) + assert!(!alive, "PID 4000000 should not be alive"); + } + + #[test] + fn daemon_info_serialization_roundtrip() { + let info = DaemonInfo { + port: 15888, + pid: 12345, + started_at: "2026-05-30 12:00:00".to_string(), + }; + let json = serde_json::to_string(&info).unwrap(); + let parsed: DaemonInfo = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed.port, 15888); + assert_eq!(parsed.pid, 12345); + } + + #[test] + fn managed_sandbox_serialization() { + let sb = ManagedSandbox { + id: "abcd1234".to_string(), + kind: InstanceKind::Cli { + command: "zsh".to_string(), + args: vec![], + }, + status: InstanceStatus::Running, + port: 0, + pty_pid: Some(1234), + window_id: None, + }; + let json = serde_json::to_string(&sb).unwrap(); + let parsed: ManagedSandbox = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed.id, "abcd1234"); + assert!(matches!(parsed.status, InstanceStatus::Running)); + } + + #[test] + fn create_sandbox_request_deserialization() { + let req: CreateSandboxRequest = + serde_json::from_str(r#"{"mode": "cli", "command": "zsh", "cols": 120, "rows": 40}"#) + .unwrap(); + assert_eq!(req.mode, "cli"); + assert_eq!(req.command, Some("zsh".to_string())); + assert_eq!(req.cols, Some(120)); + } + + #[test] + fn create_sandbox_request_defaults() { + let req: CreateSandboxRequest = serde_json::from_str(r#"{"mode": "cli"}"#).unwrap(); + assert_eq!(req.mode, "cli"); + assert!(req.command.is_none()); + assert!(req.args.is_empty()); + assert!(req.cols.is_none()); + assert!(req.rows.is_none()); + } + + // ── Router-level tests ───────────────────────────────────── + + fn test_daemon_state() -> Arc> { + Arc::new(Mutex::new(DaemonState { + port: 15999, + sandboxes: HashMap::new(), + started_at: Instant::now(), + })) + } + + fn test_daemon_state_with_sandbox() -> Arc> { + let mut sandboxes = HashMap::new(); + sandboxes.insert( + "test-sb".to_string(), + ManagedSandbox { + id: "test-sb".to_string(), + kind: InstanceKind::Cli { + command: "zsh".to_string(), + args: vec![], + }, + status: InstanceStatus::Running, + port: 0, + pty_pid: None, + window_id: None, + }, + ); + Arc::new(Mutex::new(DaemonState { + port: 15999, + sandboxes, + started_at: Instant::now(), + })) + } + + fn test_daemon_router() -> Router { + build_daemon_router(test_daemon_state()) + } + + fn test_daemon_router_with_sandbox() -> Router { + build_daemon_router(test_daemon_state_with_sandbox()) + } + + use axum::body::Body; + use axum::http::{self, Request}; + use tower::ServiceExt; + + #[tokio::test] + async fn health_returns_ok() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/health") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = axum::body::to_bytes(resp.into_body(), 1024).await.unwrap(); + let json: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(json["status"], "ok"); + assert_eq!(json["sandboxes"], 0); + } + + #[tokio::test] + async fn list_sandboxes_returns_empty() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/sandbox/list") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap(); + let list: Vec = serde_json::from_slice(&body).unwrap(); + assert!(list.is_empty()); + } + + #[tokio::test] + async fn create_sandbox_with_bad_mode() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/create") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"mode": "unknown"}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn close_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/ghost123/close") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn screenshot_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/sandbox/noexist/screenshot") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn click_valid_request() { + let app = test_daemon_router_with_sandbox(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/test-sb/input/click") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"x": 100, "y": 200, "button": "left"}"#)) + .unwrap(), + ) + .await + .unwrap(); + // May succeed or fail depending on macOS accessibility permissions + let status = resp.status(); + assert!( + matches!(status.as_u16(), 200 | 500), + "click should be 200 or 500, got {status}" + ); + } + + #[tokio::test] + async fn click_bad_button() { + let app = test_daemon_router_with_sandbox(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/test-sb/input/click") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"x": 100, "y": 200, "button": "turbo"}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn click_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/ghost/input/click") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"x": 100, "y": 200, "button": "left"}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn type_text_handler() { + let app = test_daemon_router_with_sandbox(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/test-sb/input/type") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"text": "hello"}"#)) + .unwrap(), + ) + .await + .unwrap(); + let status = resp.status(); + assert!( + matches!(status.as_u16(), 200 | 500), + "type should be 200 or 500, got {status}" + ); + } + + #[tokio::test] + async fn type_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/ghost/input/type") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"text": "hello"}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn key_handler() { + let app = test_daemon_router_with_sandbox(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/test-sb/input/key") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"key": "return", "modifiers": ["cmd"]}"#)) + .unwrap(), + ) + .await + .unwrap(); + let status = resp.status(); + assert!( + matches!(status.as_u16(), 200 | 500), + "key should be 200 or 500, got {status}" + ); + } + + #[tokio::test] + async fn key_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/ghost/input/key") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"key": "return", "modifiers": []}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn scroll_handler() { + let app = test_daemon_router_with_sandbox(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/test-sb/input/scroll") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from( + r#"{"x": 0, "y": 0, "direction": "down", "amount": 3}"#, + )) + .unwrap(), + ) + .await + .unwrap(); + let status = resp.status(); + assert!( + matches!(status.as_u16(), 200 | 500), + "scroll should be 200 or 500, got {status}" + ); + } + + #[tokio::test] + async fn scroll_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/ghost/input/scroll") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from( + r#"{"x": 0, "y": 0, "direction": "down", "amount": 3}"#, + )) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn windows_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/sandbox/ghost/windows") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn ui_inspect_nonexistent_sandbox() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/sandbox/ghost/ui/inspect/999") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[tokio::test] + async fn unknown_route_returns_404() { + let app = test_daemon_router(); + let resp = app + .oneshot( + Request::builder() + .uri("/nonexistent") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } +} diff --git a/crates/sandbox-core/src/diff.rs b/crates/sandbox-core/src/diff.rs new file mode 100644 index 0000000..1303f03 --- /dev/null +++ b/crates/sandbox-core/src/diff.rs @@ -0,0 +1,68 @@ +use crate::error::{AppError, Result}; +use image::{GenericImageView, Rgba}; +use serde::Serialize; + +/// Result of comparing two screenshots. +#[derive(Debug, Serialize)] +pub struct DiffResult { + pub total_pixels: u32, + pub different_pixels: u32, + pub diff_percentage: f64, + pub diff_image: Option>, +} + +/// Compare two PNG images pixel-by-pixel. +pub fn diff_images(img_a: &[u8], img_b: &[u8], threshold: u8) -> Result { + let a = image::load_from_memory(img_a) + .map_err(|e| AppError::Screenshot(format!("Failed to load image A: {e}")))?; + let b = image::load_from_memory(img_b) + .map_err(|e| AppError::Screenshot(format!("Failed to load image B: {e}")))?; + + if a.dimensions() != b.dimensions() { + return Err(AppError::BadRequest(format!( + "Image dimensions differ: {:?} vs {:?}", + a.dimensions(), + b.dimensions() + ))); + } + + let (width, height) = a.dimensions(); + let total = width * height; + let mut different: u32 = 0; + let mut diff_buf = image::RgbaImage::new(width, height); + + for y in 0..height { + for x in 0..width { + let pa: Rgba = a.get_pixel(x, y); + let pb: Rgba = b.get_pixel(x, y); + let dr = pa[0].abs_diff(pb[0]); + let dg = pa[1].abs_diff(pb[1]); + let db = pa[2].abs_diff(pb[2]); + if dr > threshold || dg > threshold || db > threshold { + different += 1; + diff_buf.put_pixel(x, y, Rgba([255, 0, 0, 255])); + } else { + diff_buf.put_pixel(x, y, pa); + } + } + } + + let mut diff_png = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut diff_png); + use image::ImageEncoder; + encoder + .write_image( + diff_buf.as_raw(), + width, + height, + image::ExtendedColorType::Rgba8, + ) + .map_err(|e| AppError::Screenshot(format!("Failed to encode diff: {e}")))?; + + Ok(DiffResult { + total_pixels: total, + different_pixels: different, + diff_percentage: (different as f64 / total as f64) * 100.0, + diff_image: Some(diff_png), + }) +} diff --git a/crates/sandbox-core/src/lib.rs b/crates/sandbox-core/src/lib.rs index 275eff4..5bb4143 100644 --- a/crates/sandbox-core/src/lib.rs +++ b/crates/sandbox-core/src/lib.rs @@ -2,10 +2,14 @@ pub mod automation; pub mod capture; +pub mod daemon; +pub mod diff; pub mod instance; pub mod logging; +pub mod player; pub mod process; pub mod pty_store; +pub mod recorder; pub mod sandbox; pub mod server; diff --git a/crates/sandbox-core/src/player.rs b/crates/sandbox-core/src/player.rs new file mode 100644 index 0000000..17ed7b4 --- /dev/null +++ b/crates/sandbox-core/src/player.rs @@ -0,0 +1,63 @@ +use crate::error::Result; +use crate::recorder::RecordedAction; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use std::time::Duration; +use tokio::time::sleep; +use tracing::info; + +/// Callback invoked for each action during playback. +pub type ActionCallback = Box< + dyn Fn( + RecordedAction, + ) -> std::pin::Pin> + Send>> + + Send + + Sync, +>; + +/// Replays actions from a JSONL file. +pub struct Player; + +impl Player { + pub async fn play(path: &Path, callback: &ActionCallback) -> Result<()> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut last_offset: u64 = 0; + + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + let action: RecordedAction = serde_json::from_str(&line)?; + + let delay = action.offset_ms.saturating_sub(last_offset); + if delay > 0 { + sleep(Duration::from_millis(delay)).await; + } + last_offset = action.offset_ms; + + info!( + "Playing action at {}ms: {:?}", + action.offset_ms, action.action + ); + callback(action).await?; + } + Ok(()) + } + + pub fn load_actions(path: &Path) -> Result> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut actions = Vec::new(); + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + actions.push(serde_json::from_str(&line)?); + } + Ok(actions) + } +} diff --git a/crates/sandbox-core/src/process/mod.rs b/crates/sandbox-core/src/process/mod.rs index 5f53b0e..64c1e8f 100644 --- a/crates/sandbox-core/src/process/mod.rs +++ b/crates/sandbox-core/src/process/mod.rs @@ -19,6 +19,11 @@ use { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProcessInfo { pub pid: u32, + /// Actual OS process ID (for PTY sessions, this is the child process PID). + /// For app spawns, this equals `pid`. For CLI PTY spawns, `pid` is the + /// internal tracked_id while `os_pid` is the real OS PID. + #[serde(default)] + pub os_pid: Option, pub name: String, pub path: Option, pub is_running: bool, @@ -99,6 +104,7 @@ impl ProcessManager { let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst); let info = ProcessInfo { pid: id, + os_pid: None, // `open` returns immediately; actual PID unknown name: app_name.clone(), path: Some(app_path.to_string()), is_running: true, @@ -264,6 +270,7 @@ impl ProcessManager { Ok(ProcessInfo { pid: tracked_id, + os_pid: child_pid, name: command.to_string(), path: None, is_running: true, @@ -296,6 +303,7 @@ impl ProcessManager { .iter() .map(|(id, s)| ProcessInfo { pid: *id, + os_pid: Some(s.child_pid).filter(|&p| p != 0), name: s.command.clone(), path: None, is_running: true, @@ -304,6 +312,14 @@ impl ProcessManager { Ok(processes) } + /// Check if a PTY session with the given tracked PID is still alive. + pub fn is_session_alive(tracked_pid: u32) -> bool { + SESSIONS + .lock() + .map(|sessions| sessions.contains_key(&tracked_pid)) + .unwrap_or(false) + } + /// Kill a process by tracked PID #[cfg(target_os = "macos")] pub fn kill_process(pid: u32) -> Result<()> { diff --git a/crates/sandbox-core/src/recorder.rs b/crates/sandbox-core/src/recorder.rs new file mode 100644 index 0000000..97f2711 --- /dev/null +++ b/crates/sandbox-core/src/recorder.rs @@ -0,0 +1,59 @@ +use crate::error::Result; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; +use std::time::Instant; + +/// A recorded action with timing information. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RecordedAction { + /// Milliseconds since recording started + pub offset_ms: u64, + /// The action type + pub action: ActionType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum ActionType { + #[serde(rename = "type")] + Type { text: String, pty: bool }, + #[serde(rename = "key")] + Key { + key: String, + modifiers: Vec, + pty: bool, + }, + #[serde(rename = "click")] + Click { x: f64, y: f64, button: String }, + #[serde(rename = "screenshot")] + Screenshot { path: String }, + #[serde(rename = "wait")] + Wait { ms: u64 }, +} + +/// Records actions to a JSONL file. +pub struct Recorder { + writer: BufWriter, + start: Instant, +} + +impl Recorder { + pub fn start(path: &PathBuf) -> Result { + let file = File::create(path)?; + Ok(Self { + writer: BufWriter::new(file), + start: Instant::now(), + }) + } + + pub fn record(&mut self, action: ActionType) -> Result<()> { + let offset_ms = self.start.elapsed().as_millis() as u64; + let entry = RecordedAction { offset_ms, action }; + serde_json::to_writer(&mut self.writer, &entry)?; + self.writer.write_all(b"\n")?; + self.writer.flush()?; + Ok(()) + } +} diff --git a/crates/sandbox-core/src/server/mod.rs b/crates/sandbox-core/src/server/mod.rs index 9fcea5d..dfa8771 100644 --- a/crates/sandbox-core/src/server/mod.rs +++ b/crates/sandbox-core/src/server/mod.rs @@ -64,11 +64,11 @@ struct ReadyzResponse { } #[derive(Deserialize)] -struct ClickRequest { - x: f64, - y: f64, +pub struct ClickRequest { + pub x: f64, + pub y: f64, #[serde(default = "default_button")] - button: String, + pub button: String, } fn default_button() -> String { @@ -76,23 +76,23 @@ fn default_button() -> String { } #[derive(Deserialize)] -struct TypeRequest { - text: String, +pub struct TypeRequest { + pub text: String, } #[derive(Deserialize)] -struct KeyRequest { - key: String, +pub struct KeyRequest { + pub key: String, #[serde(default)] - modifiers: Vec, + pub modifiers: Vec, } #[derive(Deserialize)] -struct ScrollRequest { - x: f64, - y: f64, - direction: String, - amount: i32, +pub struct ScrollRequest { + pub x: f64, + pub y: f64, + pub direction: String, + pub amount: i32, } #[derive(Deserialize)] @@ -104,8 +104,8 @@ struct DragRequest { } #[derive(Deserialize)] -struct SpawnAppRequest { - path: String, +pub struct SpawnAppRequest { + pub path: String, } #[derive(Deserialize)] @@ -152,7 +152,8 @@ struct UiValueQuery { element_id: String, } -/// Build the HTTP API router +/// Build the legacy single-instance HTTP API router. +/// For multi-sandbox daemon mode, use `crate::daemon::build_daemon_router` instead. pub fn build_router(state: Arc>) -> Router { let cors = CorsLayer::new() .allow_origin(Any) @@ -429,7 +430,7 @@ async fn pty_ws_handler( Ok(ws.on_upgrade(move |socket| handle_pty_ws(pid, socket))) } -async fn handle_pty_ws(pid: u32, socket: WebSocket) { +pub async fn handle_pty_ws(pid: u32, socket: WebSocket) { let mut rx = match ProcessManager::subscribe_output(pid) { Ok(rx) => rx, Err(e) => { diff --git a/crates/sandbox-core/tests/daemon_integration.rs b/crates/sandbox-core/tests/daemon_integration.rs new file mode 100644 index 0000000..5dffb52 --- /dev/null +++ b/crates/sandbox-core/tests/daemon_integration.rs @@ -0,0 +1,124 @@ +//! Integration tests for the daemon HTTP API. +//! +//! These tests use `tower::ServiceExt::oneshot` to test the daemon router +//! without binding to a real TCP port. + +use axum::body::Body; +use axum::http::{self, Request, StatusCode}; +use sandbox_core::daemon::{build_daemon_router, DaemonState}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::Mutex; +use tower::ServiceExt; + +fn empty_state() -> Arc> { + Arc::new(Mutex::new(DaemonState { + port: 0, + sandboxes: HashMap::new(), + started_at: std::time::Instant::now(), + })) +} + +fn router() -> axum::Router { + build_daemon_router(empty_state()) +} + +#[tokio::test] +async fn health_endpoint_returns_ok() { + let resp = router() + .oneshot( + Request::builder() + .uri("/health") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::OK); + let body = axum::body::to_bytes(resp.into_body(), 1024).await.unwrap(); + let json: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(json["status"], "ok"); + assert_eq!(json["sandboxes"], 0); +} + +#[tokio::test] +async fn list_sandboxes_returns_empty_array() { + let resp = router() + .oneshot( + Request::builder() + .uri("/sandbox/list") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::OK); + let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap(); + let list: Vec = serde_json::from_slice(&body).unwrap(); + assert!(list.is_empty()); +} + +#[tokio::test] +async fn create_sandbox_rejects_unknown_mode() { + let resp = router() + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/create") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"mode": "invalid"}"#)) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); +} + +#[tokio::test] +async fn close_nonexistent_returns_404() { + let resp = router() + .oneshot( + Request::builder() + .method("POST") + .uri("/sandbox/no-such-id/close") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn screenshot_nonexistent_returns_404() { + let resp = router() + .oneshot( + Request::builder() + .uri("/sandbox/no-such-id/screenshot") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn unknown_route_returns_404() { + let resp = router() + .oneshot( + Request::builder() + .uri("/does/not/exist") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::NOT_FOUND); +} diff --git a/crates/sandbox-core/tests/diff_test.rs b/crates/sandbox-core/tests/diff_test.rs new file mode 100644 index 0000000..7daab03 --- /dev/null +++ b/crates/sandbox-core/tests/diff_test.rs @@ -0,0 +1,64 @@ +use sandbox_core::diff::diff_images; + +fn encode_png(img: &image::RgbaImage) -> Vec { + let mut buf = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut buf); + use image::ImageEncoder; + encoder + .write_image( + img.as_raw(), + img.width(), + img.height(), + image::ExtendedColorType::Rgba8, + ) + .unwrap(); + buf +} + +#[test] +fn test_identical_images_return_zero_diff() { + let img = image::RgbaImage::from_pixel(2, 2, image::Rgba([255, 0, 0, 255])); + let buf = encode_png(&img); + + let result = diff_images(&buf, &buf, 10).unwrap(); + assert_eq!(result.different_pixels, 0); + assert_eq!(result.diff_percentage, 0.0); +} + +#[test] +fn test_different_images_detect_changes() { + let red = image::RgbaImage::from_pixel(2, 2, image::Rgba([255, 0, 0, 255])); + let blue = image::RgbaImage::from_pixel(2, 2, image::Rgba([0, 0, 255, 255])); + + let result = diff_images(&encode_png(&red), &encode_png(&blue), 10).unwrap(); + assert_eq!(result.different_pixels, 4); + assert!((result.diff_percentage - 100.0).abs() < 0.01); +} + +#[test] +fn test_threshold_sensitivity() { + let a = image::RgbaImage::from_pixel(1, 1, image::Rgba([100, 100, 100, 255])); + let b = image::RgbaImage::from_pixel(1, 1, image::Rgba([105, 100, 100, 255])); + + // Threshold 10: difference of 5 should NOT be detected + let result = diff_images(&encode_png(&a), &encode_png(&b), 10).unwrap(); + assert_eq!(result.different_pixels, 0); + + // Threshold 3: difference of 5 SHOULD be detected + let result = diff_images(&encode_png(&a), &encode_png(&b), 3).unwrap(); + assert_eq!(result.different_pixels, 1); +} + +#[test] +fn test_diff_image_is_generated() { + let a = image::RgbaImage::from_pixel(2, 2, image::Rgba([255, 0, 0, 255])); + let b = image::RgbaImage::from_pixel(2, 2, image::Rgba([0, 0, 255, 255])); + + let result = diff_images(&encode_png(&a), &encode_png(&b), 10).unwrap(); + assert!(result.diff_image.is_some()); + let diff_img = result.diff_image.unwrap(); + assert!(!diff_img.is_empty()); + // Verify it's a valid PNG + let loaded = image::load_from_memory(&diff_img); + assert!(loaded.is_ok()); +} diff --git a/crates/sandbox-core/tests/process_integration.rs b/crates/sandbox-core/tests/process_integration.rs index efb0553..e3f33b8 100644 --- a/crates/sandbox-core/tests/process_integration.rs +++ b/crates/sandbox-core/tests/process_integration.rs @@ -4,6 +4,7 @@ use sandbox_core::process::ProcessInfo; fn process_info_serialization_roundtrip() { let info = ProcessInfo { pid: 1001, + os_pid: None, name: "echo".to_string(), path: Some("/bin/echo".to_string()), is_running: true, @@ -24,6 +25,7 @@ fn process_info_serialization_roundtrip() { fn process_info_not_running() { let info = ProcessInfo { pid: 0, + os_pid: None, name: "dead".to_string(), path: None, is_running: false, @@ -46,12 +48,14 @@ fn process_info_vec_roundtrip() { let infos = vec![ ProcessInfo { pid: 1, + os_pid: None, name: "a".into(), path: None, is_running: true, }, ProcessInfo { pid: 2, + os_pid: None, name: "b".into(), path: None, is_running: false, @@ -68,6 +72,7 @@ fn process_info_vec_roundtrip() { fn process_info_large_pid() { let info = ProcessInfo { pid: u32::MAX, + os_pid: None, name: "max".into(), path: None, is_running: true, @@ -88,6 +93,7 @@ fn process_info_ignores_unknown_fields() { fn process_info_empty_name() { let info = ProcessInfo { pid: 1, + os_pid: None, name: String::new(), path: None, is_running: true, @@ -101,6 +107,7 @@ fn process_info_empty_name() { fn process_info_path_none_serializes_as_null() { let info = ProcessInfo { pid: 1, + os_pid: None, name: "x".into(), path: None, is_running: true, @@ -113,6 +120,7 @@ fn process_info_path_none_serializes_as_null() { fn process_info_path_some_serializes_as_string() { let info = ProcessInfo { pid: 1, + os_pid: None, name: "x".into(), path: Some("/usr/bin/x".into()), is_running: true, diff --git a/crates/sandbox-daemon/Cargo.toml b/crates/sandbox-daemon/Cargo.toml new file mode 100644 index 0000000..bdbb80c --- /dev/null +++ b/crates/sandbox-daemon/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sandbox-daemon" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description = "Sandbox daemon process — manages all sandbox instances" + +[dependencies] +sandbox-core = { workspace = true, features = ["screencapturekit"] } +tokio.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true diff --git a/crates/sandbox-daemon/build.rs b/crates/sandbox-daemon/build.rs new file mode 100644 index 0000000..ed985c3 --- /dev/null +++ b/crates/sandbox-daemon/build.rs @@ -0,0 +1,6 @@ +fn main() { + #[cfg(target_os = "macos")] + { + println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift"); + } +} diff --git a/crates/sandbox-daemon/src/main.rs b/crates/sandbox-daemon/src/main.rs new file mode 100644 index 0000000..ba4b241 --- /dev/null +++ b/crates/sandbox-daemon/src/main.rs @@ -0,0 +1,13 @@ +// crates/sandbox-daemon/src/main.rs +fn main() { + tracing_subscriber::fmt::init(); + + let port = sandbox_core::daemon::find_available_port(15801, 15899) + .expect("No available port in range 15801-15899"); + + tracing::info!("Sandbox daemon started on port {port}"); + + let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime"); + rt.block_on(async move { sandbox_core::daemon::run_daemon(port).await }) + .expect("Daemon exited with error"); +} diff --git a/docs/design/electron-rust-architecture.md b/docs/design/electron-rust-architecture.md new file mode 100644 index 0000000..7ee408f --- /dev/null +++ b/docs/design/electron-rust-architecture.md @@ -0,0 +1,616 @@ +# 方案 A:Electron + Rust Daemon 架构设计 + +> 日期:2026-05-30 +> 分支:`design/electron-rust-architecture` +> 状态:草案 + +## 一、背景与动机 + +### 1.1 当前架构 + +``` +system-test-sandbox (Tauri 2.11.2) +├── Rust 后端:直接在 Tauri 进程内调用 macOS API +├── WKWebView 渲染:macOS 系统 WebKit(Safari 内核) +└── 多实例:每个 sandbox start 启动一个独立 Tauri 进程 +``` + +每个沙箱 = 一个 Tauri 进程 (~30MB),进程级强隔离。 + +### 1.2 核心问题 + +| 问题 | 表现 | 影响 | +|------|------|------| +| **WKWebView setTimeout 失效** | xterm.js WriteBuffer 的后续 setTimeout(0) 不触发 | TUI 应用空白屏(已通过 writeDirect 绕过) | +| **终端渲染残留** | Claude Code 中旧内容未被正确擦除,出现鬼影文字 | 影响可用性,无法通过代码完全修复 | +| **渲染流畅度** | 与 waveterm (Electron/Chromium) 和 VS Code 对比有差距 | 核心体验问题 | +| **无 DevTools** | 调试前端困难 | 开发效率低 | + +**根因:WebKit 的 Canvas 渲染管线对高频终端输出的处理不如 Chromium 成熟。** `writeDirect` 解决了 setTimeout 卡死,但 WebKit 的 Canvas 合成器在处理 ANSI 光标移动/擦除指令时仍然会产生渲染残留。 + +### 1.3 为什么不直接换 WebView2 + +WebView2 是 Windows 专属的。Tauri 在各平台的渲染引擎: + +| 平台 | Tauri 渲染引擎 | 终端渲染质量 | +|------|---------------|-------------| +| macOS | WKWebView (WebKit) | 有问题 | +| Windows | WebView2 (Chromium) | 正常 | +| Linux | WebKitGTK | 可能有类似问题 | + +macOS 上只能用 WKWebView 或换 Electron/CEF。 + +## 二、目标架构 + +### 2.1 架构图 + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ CLI / Agent / 用户 │ +│ sandbox start sandbox screenshot --id abc │ +│ sandbox list sandbox click --id abc 100 200 │ +└───────────────────────────────┬──────────────────────────────────┘ + │ HTTP (localhost:port) + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Electron 主进程 (单实例, requestSingleInstanceLock) │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ Tab Manager (Tabs/Workspaces) │ │ +│ │ │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ +│ │ │ Tab A │ │ Tab B │ │ Tab C │ │ │ +│ │ │ CLI mode │ │ CLI mode │ │ APP mode │ │ │ +│ │ │ xterm.js │ │ xterm.js │ │ 截图预览+控制面板 │ │ │ +│ │ │ (Chromium) │ │ (Chromium) │ │ (Chromium) │ │ │ +│ │ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │ │ +│ └────────┼────────────────┼──────────────────┼───────────────┘ │ +│ │ │ │ │ +│ ↕ WS (直连 daemon,不经 Electron 主进程中转) │ +│ ┌────────────────────────┬───────────────────────────────────┐ │ +│ │ IPC Bridge (Electron 主进程) │ │ +│ │ 仅用于:Tab 创建/销毁通知,daemon 状态监控 │ │ +│ └────────────────────────┬───────────────────────────────────┘ │ +│ │ HTTP (控制请求) + WS (PTY 输出) │ +│ ┌────────────────────────▼───────────────────────────────────┐ │ +│ │ sandbox-daemon (Rust 子进程, 单实例) │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │ │ +│ │ │ PTY Manager │ │ App Manager │ │ Automation Engine │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ 每个 CLI │ │ NSWorkspace │ │ ScreenCaptureKit │ │ │ +│ │ │ 沙箱独立 │ │ launch + │ │ CGEvent │ │ │ +│ │ │ PTY 进程 │ │ 进程追踪 │ │ AXUIElement │ │ │ +│ │ └─────────────┘ └──────────────┘ └───────────────────┘ │ │ +│ │ │ │ +│ │ ┌──────────────────────────────────────────────────────┐ │ │ +│ │ │ HTTP Server + WebSocket Server │ │ │ +│ │ │ :15801 (默认,占用时自动递增) │ │ │ +│ │ └──────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌──────────────────────────────────────────────────────┐ │ │ +│ │ │ Instance Registry (~/.sandbox/instances/) │ │ │ +│ │ └──────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 设计原则 + +1. **Rust daemon 做所有系统级工作** — PTY、截图、输入模拟、UI 检查、APP 启动 +2. **Electron 只做 UI** — Tab 管理、xterm.js 渲染、控制面板、DevTools +3. **CLI 直连 daemon** — `sandbox screenshot --id abc` 不经过 Electron,直接 HTTP 到 daemon +4. **单 Electron 实例 + 单 daemon 实例** — 类似 waveterm 的 `requestSingleInstanceLock` 模式 +5. **守护进程保活** — 任一组件崩溃可恢复 + +## 三、组件设计 + +### 3.1 sandbox-daemon (Rust 子进程) + +**职责:** 所有 macOS 系统 API 调用 + PTY 管理 + HTTP API 服务 + +这是当前 `sandbox-core` 的能力提取为一个独立长期运行的 daemon 进程。 + +``` +sandbox-daemon +├── HTTP API Server (axum, 与当前 server/mod.rs 接口兼容) +│ ├── GET /health +│ ├── GET /sandbox/list # 列出所有沙箱 +│ ├── POST /sandbox/create # 创建新沙箱 (CLI 或 APP 模式) +│ ├── POST /sandbox/:id/close # 关闭沙箱 +│ ├── GET /sandbox/:id/screenshot # 截图 +│ ├── POST /sandbox/:id/input/click # 点击 +│ ├── POST /sandbox/:id/input/type # 输入 +│ ├── POST /sandbox/:id/input/key # 按键 +│ ├── GET /sandbox/:id/pty/ws/:pid # PTY WebSocket +│ ├── POST /sandbox/:id/app/spawn # 启动 .app +│ └── ... +├── PTY Manager +│ ├── 每个 CLI 沙箱拥有独立 PTY 进程 +│ ├── PTY Reader 线程 (当前架构已有) +│ └── WebSocket 推送 PTY 输出到 Electron +├── App Manager +│ ├── NSWorkspace launch (当前 spawn_app 已实现) +│ ├── 进程状态追踪 +│ └── SCWindow ID 发现 +├── Automation Engine +│ ├── CGEvent 输入模拟 (当前 cg_event.rs) +│ ├── AXUIElement UI 检查 (当前 ax_ui.rs) +│ └── ScreenCaptureKit 截图 (当前 capture/mod.rs) +└── Instance Registry + └── ~/.sandbox/instances/ (当前 instance/mod.rs) +``` + +**关键设计:** daemon 的 HTTP API 接口与当前 `/crates/sandbox-core/src/server/mod.rs` 基本一致,只是从"每实例一个 server"变为"一个 daemon 服务所有沙箱"。 + +**可复用的现有代码(约 2,500 行 Rust):** +- `automation/cg_event.rs` — 373 行,直接复用 +- `automation/ax_ui.rs` — 497 行,直接复用 +- `capture/mod.rs` — 441 行,直接复用 +- `process/mod.rs` — 509 行,直接复用 +- `instance/mod.rs` — 343 行,直接复用 +- `server/mod.rs` — 1,709 行,需重构为多沙箱路由 + +### 3.2 Electron 主进程 + +**职责:** Tab 管理、UI 渲染、与 daemon 通信 + +``` +electron-app/ +├── main.ts # 入口:requestSingleInstanceLock, spawn daemon +├── window.ts # 窗口管理(单 BrowserWindow + Tab 切换) +├── tab-manager.ts # Tab 创建/切换/销毁 +├── daemon-bridge.ts # 与 sandbox-daemon 的 IPC 通信 +├── preload.ts # 安全的 IPC 桥接 +├── tray.ts # 系统托盘(daemon 后台运行) +└── platform/ + ├── darwin.ts # macOS 特定逻辑 + └── win32.ts # Windows 特定逻辑 +``` + +**Tab 管理策略(参考 waveterm 的 WaveTabView):** + +```typescript +// 每个 Tab 对应一个沙箱 +interface SandboxTab { + id: string; // sandbox-id + kind: "cli" | "app"; + title: string; + webContentsView: WebContentsView; // Electron 内嵌视图 + daemonConn: DaemonConnection; // 与 daemon 的 WebSocket 连接 +} + +// Tab 切换:把目标 Tab 的 webContentsView 移到屏幕内 +// 非活跃 Tab 移到屏幕外 (x: -15000),与 waveterm 策略一致 +function switchTab(targetId: string) { + for (const tab of tabs) { + if (tab.id === targetId) { + tab.webContentsView.setBounds({ x: 0, y: 0, width, height }); + } else { + tab.webContentsView.setBounds({ x: -15000, y: -15000, width, height }); + } + } +} +``` + +**Tab 内渲染内容:** + +- **CLI 模式:** xterm.js 终端,WebSocket 直连 daemon(不经 Electron 主进程中转),获得 PTY 输出。**标准 `term.write()` 即可,无需 writeDirect。** +- **APP 模式:** 控制面板 + 定时截图预览。显示已启动 APP 的状态、截图缩略图、操作按钮。 + +**Daemon 端口发现:** + +daemon 固定监听 `:15801`(单实例无需动态分配)。启动时将端口信息写入 `~/.sandbox/daemon.json`: + +```json +{ "port": 15801, "pid": 12345, "started_at": "2026-05-30T10:00:00Z" } +``` + +Electron 启动时读取此文件发现 daemon 端口。CLI 也通过此文件定位 daemon。 + +### 3.3 通信协议 + +**参考 waveterm 和 VSCode 的通信架构:** + +| 项目 | PTY host 进程 | 前端通信方式 | 特点 | +|------|--------------|-------------|------| +| waveterm | Go wavesrv(子进程) | 单一 WebSocket(RPC + 数据流复用) | 简单,所有通信走一个连接 | +| VSCode | Node.js UtilityProcess | Electron MessagePort IPC | 最原生 Electron 方式,零网络开销 | +| **我们** | Rust daemon(子进程) | Electron IPC + 本地 HTTP(见下文) | 结合两者优势 | + +**我们的方案:双层通信** + +``` +┌─────────────────────────────────────────────────────────┐ +│ Electron renderer (Tab/xterm.js) │ +│ │ +│ ① 控制请求(创建沙箱、截图、输入模拟) │ +│ → Electron main process → spawn daemon 子进程 │ +│ → daemon HTTP API │ +│ │ +│ ② PTY 输出流(高频,延迟敏感) │ +│ → WebSocket 直连 daemon(不经 Electron main process) │ +│ → xterm.js 标准 term.write() │ +│ │ +│ ③ 事件通知(沙箱退出、进程状态变化) │ +│ → 同一个 WebSocket 连接上的 JSON 事件消息 │ +└─────────────────────────────────────────────────────────┘ + +CLI → daemon: + → 直接 HTTP 到 daemon(不经过 Electron) +``` + +**为什么选择 WebSocket 而非纯 RPC:** + +- waveterm 的方案是单一 WebSocket 复用 RPC 和数据流,简洁实用 +- VSCode 用 MessagePort 是因为 PTY host 是 Node.js 进程,天然支持 Electron IPC +- 我们的 daemon 是 Rust 进程,不支持 Electron MessagePort,WebSocket 是最自然的桥接方式 +- WebSocket 的二进制帧传输 PTY 输出效率很高(无需 base64) +- RPC 语义(请求-响应)可以在 WebSocket 上层实现(JSON 消息带 request_id) + +**WebSocket 消息格式(复用单一连接):** + +``` +PTY 输出(daemon → 前端,二进制帧): + 直接发送原始 PTY bytes,不编码 + +控制命令(前端 → daemon,JSON 帧): + { "type": "rpc", "id": "req-123", "command": "resize", "sandbox_id": "abc", "cols": 80, "rows": 24 } + { "type": "rpc", "id": "req-124", "command": "input", "sandbox_id": "abc", "data": "ls\n" } + +控制响应(daemon → 前端,JSON 帧): + { "type": "rpc_response", "id": "req-123", "success": true } + { "type": "rpc_response", "id": "req-124", "success": true } + +事件通知(daemon → 前端,JSON 帧): + { "type": "event", "event": "sandbox_exit", "sandbox_id": "abc", "exit_code": 0 } + { "type": "event", "event": "app_window_changed", "sandbox_id": "def" } +``` + +**HTTP API(供 CLI 直接调用,与当前接口兼容):** + +``` +GET /health +GET /sandbox/list +POST /sandbox/create { mode, command, args } +POST /sandbox/:id/close +GET /sandbox/:id/screenshot +POST /sandbox/:id/input/click { x, y, button } +POST /sandbox/:id/input/type { text } +POST /sandbox/:id/input/key { key, modifiers } +POST /sandbox/:id/app/spawn { app_path } +``` + +### 3.4 Daemon 端口发现 + +**端口分配策略:** + +- 默认端口 `15801` +- 如果被占用,依次尝试 `15802`, `15803`, ... `15899` +- daemon 启动时打印端口信息:`Sandbox daemon started on port 15801` + +**CLI 发现 daemon 端口:** + +``` +1. 读取 ~/.sandbox/daemon.json + { "port": 15802, "pid": 12345, "started_at": "2026-05-30T10:00:00Z" } + +2. 验证 pid 是否存活(kill(pid, 0)) + - 存活 → 使用该端口 + - 不存在 → 清理 daemon.json,视为 daemon 未运行 + +3. daemon 未运行 → CLI 负责启动 daemon,等待 daemon.json 写入后读取端口 +``` + +Electron 启动时也通过同样机制发现 daemon 端口。 + +### 3.5 CLI 集成 + +**当前架构:** +```bash +sandbox start claude + → CLI 解析参数 + → spawn 一个新的 Tauri 进程 (system-test-sandbox --mode=cli --cmd=claude) + → Tauri 进程内嵌 HTTP server + → 注册实例到 ~/.sandbox/instances/ +``` + +**新架构:** +```bash +sandbox start claude + → CLI 解析参数 + → 检查 daemon 是否运行(读 ~/.sandbox/daemon.json + 验证 pid) + → 如果 daemon 未运行: + spawn sandbox-daemon + 等待 ~/.sandbox/daemon.json 出现 + 打印 "Sandbox daemon started on port 15801" + → 如果 daemon 已运行: + 打印 "Sandbox daemon running on port 15802" + → 检查 Electron 是否运行(同上机制,~/.sandbox/electron.json) + → 如果 Electron 未运行:spawn electron-app + → HTTP POST http://localhost:{port}/sandbox/create { mode: "cli", command: "claude" } + → daemon 创建 PTY 进程,返回 { sandbox_id: "abc123", pid: 12345 } + → daemon 通过 WebSocket 通知 Electron 创建新 Tab + → 打印 "Sandbox abc123 created (port 15801)" +``` + +**CLI 操作命令(不变):** +```bash +sandbox screenshot --id abc → HTTP GET daemon/sandbox/abc/screenshot +sandbox click --id abc 100 200 → HTTP POST daemon/sandbox/abc/input/click +sandbox list → HTTP GET daemon/sandbox/list +sandbox close abc → HTTP POST daemon/sandbox/abc/close → Electron 关闭对应 Tab +``` + +CLI 操作不经过 Electron,直接与 daemon 通信。对于需要切换 Tab 的操作(如截图前切换),daemon 通过 WebSocket 通知 Electron 切换 Tab。 + +## 四、操作流程 + +### 4.1 CLI 模式沙箱 + +``` +用户: sandbox start claude + +1. CLI 检查 daemon → 未运行 → spawn sandbox-daemon → 打印端口 +2. CLI 检查 Electron → 未运行 → spawn electron-app +3. CLI → HTTP POST http://localhost:{port}/sandbox/create { mode: "cli", command: "claude" } +4. Daemon: 创建 PTY 进程 (zsh → claude) +5. Daemon: 返回 { sandbox_id: "abc123", pid: 12345 } +6. Daemon: 通过 WebSocket 事件通知 Electron "新沙箱已创建" +7. Electron: 创建新 Tab,建立 WebSocket 连接到 daemon +8. Electron: xterm.js 使用标准 term.write() 渲染(Chromium,无 WKWebView 问题) +``` + +### 4.2 APP 模式沙箱 + +``` +用户: sandbox start /Applications/cc-switch.app + +1-3. 同上 +4. Daemon: NSWorkspace.open("cc-switch.app") +5. Daemon: 等待窗口出现,获取 SCWindow ID +6. Daemon: 返回 { sandbox_id: "def456", window_id: 789 } +7. Electron: 创建新 Tab(APP 控制面板模式) +8. cc-switch 作为独立 macOS 窗口运行,不在 Electron 内 +9. Daemon: ScreenCaptureKit 按 window_id 截图 → 返回给 CLI +``` + +**APP 不在 Electron 里运行。** Electron 的 Tab 只是控制面板。cc-switch 的窗口在 macOS 桌面上独立存在。 + +### 4.3 沙箱作用域操作 + +``` +用户: sandbox screenshot --id abc -o result.png + +1. CLI → HTTP GET daemon/sandbox/abc/screenshot +2. Daemon: 通知 Electron 切换到 Tab abc(如果不在前台) +3. Electron: 切换 Tab +4. Daemon: ScreenCaptureKit 截取沙箱窗口 +5. Daemon: 返回 PNG 数据给 CLI +6. CLI: 写入 result.png +7. Electron 窗口保持在桌面后台(不需要在最前面) +``` + +## 五、强隔离策略 + +### 5.1 隔离边界 + +| 层面 | 隔离机制 | 崩溃影响 | +|------|----------|----------| +| **PTY 进程** | 每个 CLI 沙箱独立 OS 进程 | 只影响该沙箱的终端 | +| **APP 进程** | macOS 独立进程 | 只影响该 APP | +| **Electron renderer** | 每个 Tab 独立 WebContentsView(Chromium 沙箱) | 只影响该 Tab 的 UI | +| **Electron 主进程** | 单点 | 所有 Tab UI 丢失 | +| **Rust daemon** | 单点 | 所有截图/输入/PTY 能力丢失 | + +### 5.2 崩溃恢复 + +**Electron 主进程崩溃:** +``` +守护进程检测到 Electron 退出 + → 从 daemon 获取活跃沙箱列表 + → 重启 Electron + → 为每个沙箱重新创建 Tab + → CLI 沙箱:PTY 进程还活着,WebSocket 重连即可恢复终端 + → APP 沙箱:APP 窗口还在,重新关联即可 +``` + +**Rust daemon 崩溃:** +``` +Electron 检测到 daemon 连接断开 + → 重启 daemon + → PTY 进程已随 daemon 退出而终止(PTY fd 关闭) + → APP 进程还在(独立进程),但需要重新注册 + → 标记 CLI 沙箱为 "disconnected",通知用户 +``` + +**单个 PTY 进程崩溃:** +``` +只影响该沙箱 + → daemon 通知 Electron 该沙箱退出 + → Tab 显示 "进程已退出" 提示 + → 其他沙箱不受影响 +``` + +### 5.3 与当前 Tauri 架构的隔离对比 + +| 场景 | Tauri 多实例 | Electron 单进程 | +|------|-------------|----------------| +| 单个 PTY 崩溃 | 只影响该实例 | 只影响该 Tab | +| UI 渲染器崩溃 | 只影响该实例 | 只影响该 Tab(Chromium renderer 隔离) | +| 主进程崩溃 | 只影响该实例(其他实例完全独立) | **所有 Tab 丢失**,需守护进程恢复 | +| daemon 崩溃 | N/A(无 daemon) | **所有能力丢失**,需重启 daemon | + +**结论:** 日常场景(PTY 崩溃、渲染器崩溃)隔离效果等同。极端场景(主进程/daemon 崩溃)需要守护进程恢复,但这是罕见事件。 + +## 六、跨平台路径(未来) + +当前阶段仅聚焦 macOS。未来扩展到 Windows 时: + +- **Electron (Chromium)** 跨平台渲染行为一致,前端代码无需修改 +- **sandbox-daemon (Rust)** 需要实现 Windows 系统API:SendInput(输入模拟)、DXGI Desktop Duplication(截图)、conpty(PTY) +- 通过 Rust trait 抽象平台差异: + +```rust +trait AutomationEngine { + fn click(&self, x: f64, y: f64, button: MouseButton) -> Result<()>; + fn type_text(&self, text: &str) -> Result<()>; + fn press_key(&self, key: &str, modifiers: &[&str]) -> Result<()>; + fn capture_window(&self, window_id: u64) -> Result>; +} + +#[cfg(target_os = "macos")] +struct MacOsEngine { /* CGEvent + AXUIElement + ScreenCaptureKit */ } +``` + +**Electron 选择的额外好处:** 未来 Windows 版的渲染层无需任何额外适配工作。 + +## 七、目录结构变更 + +``` +system-test-sandbox/ +├── Cargo.toml # Workspace 根 +├── crates/ +│ ├── sandbox-core/ # 核心库(大部分复用) +│ │ └── src/ +│ │ ├── lib.rs, error.rs +│ │ ├── automation/ # ✅ 直接复用 +│ │ │ ├── cg_event.rs +│ │ │ └── ax_ui.rs +│ │ ├── capture/ # ✅ 直接复用 +│ │ │ └── mod.rs +│ │ ├── process/ # ✅ 直接复用 +│ │ │ └── mod.rs +│ │ ├── instance/ # ✅ 直接复用 +│ │ │ └── mod.rs +│ │ └── server/ # 🔧 重构为多沙箱路由 +│ │ └── mod.rs +│ ├── sandbox-daemon/ # 🆕 Daemon 二进制 (从 sandbox-core 构建) +│ │ └── src/ +│ │ └── main.rs # daemon 入口:启动 HTTP server + PTY 管理 +│ └── sandbox-cli/ # 🔧 修改:spawn daemon+electron 而非 Tauri +│ └── src/ +│ ├── main.rs +│ ├── client.rs +│ └── mcp_server.rs +├── electron-app/ # 🆕 Electron 应用(替代 src-tauri) +│ ├── package.json +│ ├── electron-builder.config.cjs +│ ├── main.ts # Electron 主进程 +│ ├── preload.ts +│ └── src/ +│ ├── window.ts # 窗口管理 +│ ├── tab-manager.ts # Tab 创建/切换 +│ ├── daemon-bridge.ts # Daemon IPC +│ └── tray.ts # 系统托盘 +├── sandbox-web/ # 🔧 修改:去掉 writeDirect,用标准 term.write() +│ └── src/ +│ ├── main.tsx +│ ├── api.ts # 修改为连接 daemon 而非内嵌 HTTP +│ └── components/ +│ ├── Terminal.tsx # 去掉 writeDirect +│ ├── Dashboard.tsx +│ ├── Sidebar.tsx +│ └── AppControlPanel.tsx # 🆕 APP 模式控制面板 +├── src-tauri/ # ❌ 删除 +└── docs/ + └── design/ + └── electron-rust-architecture.md # 本文件 +``` + +## 八、可复用代码评估 + +| 模块 | 当前行数 | 改动量 | 说明 | +|------|---------|--------|------| +| `automation/cg_event.rs` | 373 | 无 | 直接复用,daemon 内调用 | +| `automation/ax_ui.rs` | 497 | 无 | 直接复用 | +| `capture/mod.rs` | 441 | 小改 | 复用,可能需要调整窗口发现逻辑 | +| `process/mod.rs` | 509 | 小改 | 复用 PTY spawn 和 APP launch | +| `instance/mod.rs` | 343 | 中改 | 从每实例注册改为 daemon 统一管理 | +| `server/mod.rs` | 1,709 | 重构 | 从每实例 server 改为 daemon 多沙箱路由 | +| `sandbox/mod.rs` | 262 | 重构 | Sandbox 结构体适配 daemon 模式 | +| `sandbox-cli/main.rs` | 758 | 重构 | 改为 spawn daemon + Electron | +| `sandbox-cli/client.rs` | 700 | 小改 | HTTP client 基本不变 | +| **Rust 合计** | **~5,592** | **~40% 可直接复用** | | +| | | | | +| `Terminal.tsx` | 180 | 删 writeDirect | 标准化 | +| `api.ts` | 318 | 修改 | 连接 daemon | +| `Dashboard.tsx` | 194 | 修改 | 多 Tab 布局 | +| `Sidebar.tsx` | 138 | 修改 | Tab 列表 | +| `src-tauri/main.rs` | 340 | **删除** | 被 Electron main.ts 替代 | + +## 九、实施阶段 + +### Phase 1:sandbox-daemon(Rust 侧) + +**目标:** 让 sandbox-daemon 成为独立可运行进程,管理多个沙箱。 + +1. 创建 `crates/sandbox-daemon/` binary crate +2. 重构 `server/mod.rs` 为多沙箱路由(`/sandbox/:id/...`) +3. 添加 daemon 生命周期管理(pid 文件、信号处理、优雅关闭) +4. 修改 CLI:`sandbox start` → spawn daemon + 发送创建请求 +5. 验证:CLI 通过 daemon 的 HTTP API 完成 PTY 启动、截图、输入模拟 + +**验证标准:** `sandbox start claude` 通过 daemon 启动 PTY,`sandbox screenshot` 通过 daemon 截图,无 Electron 参与。 + +### Phase 2:Electron 壳 + +**目标:** 用 Electron 替代 Tauri 窗口。 + +1. 搭建 `electron-app/` 项目(electron-vite 或 electron-forge) +2. 实现 `main.ts`:`requestSingleInstanceLock`,spawn daemon,创建 BrowserWindow +3. 实现 Tab Manager:WebContentsView 管理,Tab 切换 +4. 修改前端:`api.ts` 连接 daemon,`Terminal.tsx` 去掉 writeDirect +5. 实现 APP 模式控制面板 Tab +6. 实现 `sandbox start` 时的 second-instance 处理(已有实例时创建新 Tab) + +**验证标准:** `sandbox start claude` 打开 Electron 窗口,xterm.js 使用标准 `term.write()` 正常渲染 Claude Code。 + +### Phase 3:守护与恢复 + +**目标:** 处理崩溃恢复,提升可靠性。 + +1. Electron → daemon 心跳检测 +2. daemon → Electron 状态同步 +3. 崩溃恢复:守护进程自动重启 + 状态恢复 +4. 系统托盘:daemon 后台运行,Electron 关闭窗口不退出 + +### Phase 4:完善与优化 + +**目标:** 生产级稳定性。 + +1. 完善崩溃恢复逻辑 +2. 性能优化(大输出场景的 PTY 数据流) +3. Electron 打包配置(macOS .dmg) +4. 日志系统对接(daemon 日志与 Electron 日志统一) + +## 十、风险与缓解 + +| 风险 | 概率 | 影响 | 缓解 | +|------|------|------|------| +| Electron 主进程崩溃导致所有 Tab 丢失 | 低 | 高 | 守护进程 + PTY 进程不丢失(可重连) | +| Rust daemon 崩溃导致所有能力丢失 | 低 | 高 | Electron 监控 + 自动重启 daemon | +| 包体积从 ~30MB 增至 ~150MB+ | 确定 | 中 | CLI 分发用轻量安装器;daemon 独立分发 | +| 内存从 ~30MB/实例 增至 ~180MB 固定 + ~30MB/Tab | 确定 | 中 | 相比 N 个 Tauri 实例,3+ 沙箱时反而更省内存 | +| IPC 延迟影响 PTY 输出流畅度 | 中 | 中 | WebSocket 直连 daemon,不经 Electron 主进程中转 | +| Electron 版本升级维护成本 | 中 | 低 | 使用 Electron LTS 版本 | +| xterm.js 内部 API 变更无需再跟踪 | 确定 | 正向 | 不再需要 writeDirect hack | + +## 十一、与当前架构的对比总结 + +``` + 当前 (Tauri) 方案 A (Electron+Rust) +───────────── ────────────────── ────────────────────── +渲染引擎 WKWebView (WebKit) Chromium +终端渲染质量 有残留/鬼影 与 VS Code 一致 +writeDirect 需要(hack) 不需要(标准 term.write) +DevTools 需手动开启 原生支持 +包体积 ~30MB ~150MB+ +内存 (3沙箱) ~90MB ~210MB +隔离性 进程级强隔离 Tab 级隔离 + 守护进程恢复 +系统 API 调用 Rust FFI 直接(进程内) Rust FFI 直接(daemon 内) +IPC 复杂度 无(同进程) Electron ↔ daemon (HTTP/WS) +多实例 UX 多窗口 单窗口多 Tab +Rust 代码复用 100% ~40% 直接复用,~30% 重构 +重写工作量 0 大(~2-3 周) +``` diff --git a/docs/superpowers/plans/2026-05-30-electron-rust-daemon.md b/docs/superpowers/plans/2026-05-30-electron-rust-daemon.md new file mode 100644 index 0000000..8d13db6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-electron-rust-daemon.md @@ -0,0 +1,1071 @@ +# Electron + Rust Daemon 架构迁移实施计划 + +> **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:** 将 system-test-sandbox 从 Tauri 多实例架构迁移到 Electron + Rust daemon 单进程多 Tab 架构,解决 WKWebView 终端渲染问题。 + +**Architecture:** Rust sandbox-daemon 作为独立后台进程管理所有沙箱(PTY、截图、输入模拟、APP 启动)。Electron 作为 UI 层,通过 HTTP/WebSocket 与 daemon 通信。CLI 直接与 daemon HTTP API 交互,不经过 Electron。 + +**Tech Stack:** Rust (sandbox-daemon binary), Electron + TypeScript (UI), axum (HTTP+WS), React + xterm.js (前端) + +**Spec:** `docs/design/electron-rust-architecture.md` + +--- + +## Scope + +本计划覆盖 Phase 1(sandbox-daemon)的完整实现。Phase 1 完成后,所有 Rust 侧的系统能力(PTY、截图、输入模拟、APP 启动)都通过 daemon 的 HTTP API 可用,CLI 可以不依赖任何 UI 直接完成沙箱管理。 + +Phase 2(Electron 壳替换 Tauri)将在 Phase 1 完成后另写计划。 + +## File Structure + +``` +新增/修改文件清单: + +crates/sandbox-daemon/ # 🆕 Daemon binary crate +├── Cargo.toml +└── src/ + └── main.rs # Daemon 入口:端口分配、HTTP server、信号处理 + +crates/sandbox-core/src/ +├── daemon/ # 🆕 Daemon 生命周期管理 +│ └── mod.rs # DaemonState, 端口发现, pid 文件, 多沙箱管理 +└── server/ + └── mod.rs # 🔧 重构:多沙箱路由 (从 /sandbox/:id/... 改) + +crates/sandbox-cli/src/ +├── main.rs # 🔧 重构:spawn daemon → HTTP create +└── client.rs # 🔧 小改:适配新 API 路径 + +crates/sandbox-core/src/ +├── process/mod.rs # ✅ 直接复用 +├── automation/cg_event.rs # ✅ 直接复用 +├── automation/ax_ui.rs # ✅ 直接复用 +├── capture/mod.rs # ✅ 直接复用 +├── instance/mod.rs # ✅ 直接复用 +└── error.rs # ✅ 直接复用 +``` + +--- + +## Task 1: 创建 sandbox-daemon binary crate + +**Files:** +- Create: `crates/sandbox-daemon/Cargo.toml` +- Create: `crates/sandbox-daemon/src/main.rs` +- Modify: `Cargo.toml` (workspace members) + +- [ ] **Step 1: 创建 Cargo.toml** + +```toml +# crates/sandbox-daemon/Cargo.toml +[package] +name = "sandbox-daemon" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description = "Sandbox daemon process — manages all sandbox instances" + +[dependencies] +sandbox-core = { workspace = true, features = ["screencapturekit"] } +axum.workspace = true +tokio.workspace = true +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +uuid.workspace = true +``` + +- [ ] **Step 2: 创建 main.rs 骨架** + +```rust +// crates/sandbox-daemon/src/main.rs +use sandbox_core::daemon::DaemonState; + +fn main() { + tracing_subscriber::fmt::init(); + + let port = sandbox_core::daemon::find_available_port(15801, 15899) + .expect("No available port in range 15801-15899"); + + println!("Sandbox daemon started on port {port}"); + + let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime"); + rt.block_on(async move { + sandbox_core::daemon::run_daemon(port).await + }).expect("Daemon exited with error"); +} +``` + +- [ ] **Step 3: 添加 workspace member** + +在根 `Cargo.toml` 的 `[workspace] members` 中添加 `"crates/sandbox-daemon"`。 + +- [ ] **Step 4: 验证编译** + +Run: `cargo check -p sandbox-daemon` +Expected: 编译错误(`sandbox_core::daemon` 模块不存在),这是预期的。Task 2 解决。 + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-daemon/ Cargo.toml +git commit -m "feat(daemon): scaffold sandbox-daemon binary crate" +``` + +--- + +## Task 2: 实现 daemon 生命周期管理模块 + +**Files:** +- Create: `crates/sandbox-core/src/daemon/mod.rs` +- Modify: `crates/sandbox-core/src/lib.rs` (添加 `pub mod daemon`) + +- [ ] **Step 1: 创建 daemon/mod.rs** + +```rust +// crates/sandbox-core/src/daemon/mod.rs +use crate::error::AppError; +use crate::instance::{InstanceKind, InstanceRegistry, InstanceStatus, SandboxInstance}; +use crate::process::ProcessManager; +use axum::extract::State; +use axum::{Json, Router, routing::{get, post}}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::Mutex; + +/// Per-sandbox state tracked by the daemon +#[derive(Debug)] +pub struct ManagedSandbox { + pub id: String, + pub kind: InstanceKind, + pub status: InstanceStatus, + pub port: u16, + pub pty_pid: Option, + pub window_id: Option, + pub created_at: chrono::DateTime, +} + +/// Shared daemon state +pub struct DaemonState { + pub port: u16, + pub sandboxes: HashMap, + pub started_at: std::time::Instant, +} + +impl DaemonState { + pub fn new(port: u16) -> Self { + Self { + port, + sandboxes: HashMap::new(), + started_at: std::time::Instant::now(), + } + } +} + +#[derive(Deserialize)] +pub struct CreateSandboxRequest { + pub mode: String, // "cli" or "app" + pub command: Option, // CLI command or app path + #[serde(default)] + pub args: Vec, + #[serde(default)] + pub cols: Option, + #[serde(default)] + pub rows: Option, +} + +#[derive(Serialize)] +pub struct CreateSandboxResponse { + pub sandbox_id: String, + pub pty_pid: Option, + pub window_id: Option, +} + +#[derive(Serialize)] +pub struct ListSandboxesResponse { + pub sandboxes: Vec, +} + +#[derive(Serialize)] +pub struct SandboxSummary { + pub id: String, + pub kind: String, + pub status: String, + pub pty_pid: Option, + pub window_id: Option, +} + +/// Try binding to ports from `start` to `end` (inclusive), return first available. +pub fn find_available_port(start: u16, end: u16) -> Result { + for port in start..=end { + if std::net::TcpListener::bind(format!("127.0.0.1:{port}")).is_ok() { + return Ok(port); + } + } + Err(AppError::Process(format!( + "No available port in range {start}-{end}" + ))) +} + +/// Path to daemon metadata file +pub fn daemon_json_path() -> PathBuf { + dirs_next::home_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join(".sandbox") + .join("daemon.json") +} + +#[derive(Serialize, Deserialize)] +pub struct DaemonInfo { + pub port: u16, + pub pid: u32, + pub started_at: String, +} + +/// Write daemon metadata to ~/.sandbox/daemon.json +pub fn write_daemon_info(port: u16) -> Result<(), AppError> { + let info = DaemonInfo { + port, + pid: std::process::id(), + started_at: chrono::Utc::now().to_rfc3339(), + }; + let path = daemon_json_path(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|e| { + AppError::Process(format!("Failed to create ~/.sandbox/: {e}")) + })?; + } + let json = serde_json::to_string_pretty(&info) + .map_err(|e| AppError::Process(format!("Failed to serialize daemon info: {e}")))?; + std::fs::write(&path, json) + .map_err(|e| AppError::Process(format!("Failed to write daemon.json: {e}")))?; + Ok(()) +} + +/// Read daemon metadata from ~/.sandbox/daemon.json +pub fn read_daemon_info() -> Result { + let path = daemon_json_path(); + let json = std::fs::read_to_string(&path) + .map_err(|e| AppError::Process(format!("Failed to read daemon.json: {e}")))?; + serde_json::from_str(&json) + .map_err(|e| AppError::Process(format!("Failed to parse daemon.json: {e}"))) +} + +/// Check if a daemon is running by reading daemon.json and verifying the pid. +/// Returns the port if running, or None. +pub fn find_running_daemon() -> Option { + let info = read_daemon_info().ok()?; + // Check if pid is alive + let pid = info.pid as i32; + unsafe { + // kill(pid, 0) returns 0 if the process exists + if libc::kill(pid, 0) == 0 { + return Some(info.port); + } + } + // Stale daemon.json — clean it up + let _ = std::fs::remove_file(daemon_json_path()); + None +} + +/// Remove daemon.json (called on shutdown) +pub fn cleanup_daemon_info() { + let _ = std::fs::remove_file(daemon_json_path()); +} + +/// Build the daemon's HTTP router (multi-sandbox routes) +pub fn build_daemon_router(state: Arc>) -> Router { + let cors = tower_http::cors::CorsLayer::new() + .allow_origin(tower_http::cors::Any) + .allow_methods(tower_http::cors::Any) + .allow_headers(tower_http::cors::Any); + + Router::new() + .route("/health", get(health)) + .route("/sandbox/list", get(list_sandboxes)) + .route("/sandbox/create", post(create_sandbox)) + .route("/sandbox/{id}/close", post(close_sandbox)) + .route("/sandbox/{id}/screenshot", get(screenshot)) + .route("/sandbox/{id}/input/click", post(click)) + .route("/sandbox/{id}/input/type", post(type_text)) + .route("/sandbox/{id}/input/key", post(press_key)) + .route("/sandbox/{id}/input/scroll", post(scroll)) + .route("/sandbox/{id}/pty/ws/{pid}", get(pty_ws)) + .route("/sandbox/{id}/app/spawn", post(spawn_app)) + .route("/sandbox/{id}/windows", get(list_windows)) + .route("/sandbox/{id}/ui/inspect/{window_id}", get(ui_inspect)) + .route("/shutdown", post(shutdown)) + .layer(cors) + .with_state(state) +} + +// ── Handlers ────────────────────────────────────────────── + +async fn health(State(state): State>>) -> Json { + let s = state.lock().await; + Json(serde_json::json!({ + "status": "ok", + "port": s.port, + "uptime_secs": s.started_at.elapsed().as_secs(), + "sandbox_count": s.sandboxes.len(), + })) +} + +async fn list_sandboxes( + State(state): State>>, +) -> Json { + let s = state.lock().await; + let sandboxes: Vec = s + .sandboxes + .values() + .map(|sb| SandboxSummary { + id: sb.id.clone(), + kind: match &sb.kind { + InstanceKind::Cli { .. } => "cli".to_string(), + InstanceKind::App { .. } => "app".to_string(), + }, + status: format!("{:?}", sb.status).to_lowercase(), + pty_pid: sb.pty_pid, + window_id: sb.window_id, + }) + .collect(); + Json(ListSandboxesResponse { sandboxes }) +} + +async fn create_sandbox( + State(state): State>>, + Json(req): Json, +) -> Result, AppError> { + let id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + + let kind = match req.mode.as_str() { + "cli" => { + let command = req.command.clone().unwrap_or_else(|| "zsh".to_string()); + let cols = req.cols.unwrap_or(80); + let rows = req.rows.unwrap_or(24); + let info = tokio::task::spawn_blocking(move || { + ProcessManager::spawn_cli_with_size(&command, &req.args, cols, rows) + }) + .await + .map_err(|e| AppError::Process(format!("spawn_cli panicked: {e}")))??; + + let mut s = state.lock().await; + let sandbox = ManagedSandbox { + id: id.clone(), + kind: InstanceKind::Cli { + command, + args: req.args.clone(), + }, + status: InstanceStatus::Running, + port: s.port, + pty_pid: Some(info.pid as u32), + window_id: None, + created_at: chrono::Utc::now(), + }; + s.sandboxes.insert(id.clone(), sandbox); + + // Register in instance registry + let registry = InstanceRegistry::default(); + let instance = SandboxInstance::new( + &id, + s.port, + std::process::id(), + InstanceKind::Cli { + command: req.command.clone().unwrap_or_default(), + args: req.args.clone(), + }, + ); + let _ = registry.register(&instance); + + return Ok(Json(CreateSandboxResponse { + sandbox_id: id, + pty_pid: Some(info.pid as u32), + window_id: None, + })); + } + "app" => { + let app_path = req.command.clone().ok_or_else(|| { + AppError::BadRequest("app mode requires 'command' field with app path".into()) + })?; + InstanceKind::App { path: app_path } + } + other => { + return Err(AppError::BadRequest(format!("Unknown mode: {other}"))); + } + }; + + // Handle app mode + let app_path = match &kind { + InstanceKind::App { path } => path.clone(), + _ => unreachable!(), + }; + + let (info, window_id) = tokio::task::spawn_blocking(move || { + ProcessManager::spawn_app_with_window(&app_path) + }) + .await + .map_err(|e| AppError::Process(format!("spawn_app panicked: {e}")))??; + + let mut s = state.lock().await; + let sandbox = ManagedSandbox { + id: id.clone(), + kind, + status: InstanceStatus::Running, + port: s.port, + pty_pid: None, + window_id, + created_at: chrono::Utc::now(), + }; + s.sandboxes.insert(id.clone(), sandbox); + + // Register in instance registry + let registry = InstanceRegistry::default(); + let instance = SandboxInstance::new( + &id, + s.port, + std::process::id(), + InstanceKind::App { + path: app_path.clone(), + }, + ); + let _ = registry.register(&instance); + + Ok(Json(CreateSandboxResponse { + sandbox_id: id, + pty_pid: Some(info.pid as u32), + window_id, + })) +} + +async fn close_sandbox( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, +) -> Result, AppError> { + let mut s = state.lock().await; + if let Some(sb) = s.sandboxes.remove(&id) { + if let Some(pid) = sb.pty_pid { + let _ = ProcessManager::kill_process(pid); + } + let registry = InstanceRegistry::default(); + let _ = registry.unregister(&id); + tracing::info!("Closed sandbox {id}"); + Ok(Json(serde_json::json!({"closed": id}))) + } else { + Err(AppError::Process(format!("Sandbox {id} not found"))) + } +} + +async fn screenshot( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, +) -> Result { + let s = state.lock().await; + let sb = s.sandboxes.get(&id).ok_or_else(|| { + AppError::Process(format!("Sandbox {id} not found")) + })?; + let window_id = sb.window_id.ok_or_else(|| { + AppError::BadRequest(format!("Sandbox {id} has no window_id")) + })?; + drop(s); + + let png_data = crate::capture::ScreenCapture::capture_window(window_id)?; + Ok(( + axum::http::StatusCode::OK, + [("content-type", "image/png")], + png_data, + )) +} + +async fn click( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, + Json(req): Json, +) -> Result, AppError> { + // For now, click uses global coordinates (same as current behavior) + let button = match req.button.to_lowercase().as_str() { + "left" => crate::automation::cg_event::MouseButton::Left, + "right" => crate::automation::cg_event::MouseButton::Right, + "middle" => crate::automation::cg_event::MouseButton::Middle, + other => return Err(AppError::BadRequest(format!("Unknown button: {other}"))), + }; + crate::automation::cg_event::InputSimulator::click(req.x, req.y, button, None)?; + Ok(Json(serde_json::json!({"clicked": {"x": req.x, "y": req.y}}))) +} + +async fn type_text( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, + Json(req): Json, +) -> Result, AppError> { + crate::automation::cg_event::InputSimulator::type_text(&req.text, None)?; + Ok(Json(serde_json::json!({"typed": true}))) +} + +async fn press_key( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, + Json(req): Json, +) -> Result, AppError> { + let mod_refs: Vec<&str> = req.modifiers.iter().map(|s| s.as_str()).collect(); + crate::automation::cg_event::InputSimulator::press_key(&req.key, &mod_refs, None)?; + Ok(Json(serde_json::json!({"pressed": {"key": req.key}}))) +} + +async fn scroll( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, + Json(req): Json, +) -> Result, AppError> { + crate::automation::cg_event::InputSimulator::scroll( + req.x, req.y, &req.direction, req.amount, None, + )?; + Ok(Json(serde_json::json!({"scrolled": true}))) +} + +async fn pty_ws( + axum::extract::Path((id, pid)): axum::extract::Path<(String, u32)>, + ws: axum::extract::ws::WebSocketUpgrade, +) -> Result { + ProcessManager::subscribe_output(pid) + .map_err(|e| AppError::Process(format!("PTY {pid} not found: {e}")))?; + Ok(ws.on_upgrade(move |socket| crate::server::handle_pty_ws(pid, socket))) +} + +async fn spawn_app( + State(state): State>>, + axum::extract::Path(id): axum::extract::Path, + Json(req): Json, +) -> Result, AppError> { + let info = tokio::task::spawn_blocking(move || ProcessManager::spawn_app(&req.path)) + .await + .map_err(|e| AppError::Process(format!("spawn_app panicked: {e}")))??; + Ok(Json(serde_json::json!({"spawned": info.name, "pid": info.pid}))) +} + +async fn list_windows() -> Result>, AppError> { + let windows = tokio::task::spawn_blocking(crate::capture::ScreenCapture::list_windows) + .await + .map_err(|e| AppError::Process(format!("list_windows panicked: {e}")))??; + Ok(Json(windows)) +} + +async fn ui_inspect( + axum::extract::Path(window_id): axum::extract::Path, +) -> Result, AppError> { + let tree = tokio::task::spawn_blocking(move || { + crate::automation::ax_ui::UiInspector::inspect_window(window_id) + }) + .await + .map_err(|e| AppError::Process(format!("ui_inspect panicked: {e}")))??; + Ok(Json(tree)) +} + +async fn shutdown() -> Json { + tracing::info!("Shutdown requested via HTTP"); + cleanup_daemon_info(); + std::thread::spawn(|| { + std::thread::sleep(std::time::Duration::from_millis(100)); + std::process::exit(0); + }); + Json(serde_json::json!({"shutting_down": true})) +} + +/// Run the daemon: bind HTTP server and serve forever +pub async fn run_daemon(port: u16) -> Result<(), AppError> { + write_daemon_info(port)?; + + let state = Arc::new(Mutex::new(DaemonState::new(port))); + let router = build_daemon_router(state); + + let addr = format!("127.0.0.1:{port}"); + let listener = tokio::net::TcpListener::bind(&addr) + .await + .map_err(|e| AppError::Process(format!("Failed to bind {addr}: {e}")))?; + + tracing::info!("Daemon HTTP API listening on http://{addr}"); + + // Graceful shutdown: clean up daemon.json on ctrl-c + let shutdown_path = daemon_json_path(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.ok(); + tracing::info!("Received Ctrl+C, shutting down"); + let _ = std::fs::remove_file(&shutdown_path); + std::process::exit(0); + }); + + axum::serve(listener, router) + .await + .map_err(|e| AppError::Process(format!("HTTP server error: {e}")))?; + + Ok(()) +} +``` + +- [ ] **Step 2: 导出 ClickRequest, TypeRequest, KeyRequest, ScrollRequest, SpawnAppRequest 为 public** + +在 `crates/sandbox-core/src/server/mod.rs` 中,将以下 struct 从 private 改为 `pub`: + +```rust +pub struct ClickRequest { ... } +pub struct TypeRequest { ... } +pub struct KeyRequest { ... } +pub struct ScrollRequest { ... } +pub struct SpawnAppRequest { ... } +``` + +同时将 `handle_pty_ws` 函数改为 `pub`: + +```rust +pub async fn handle_pty_ws(pid: u32, socket: WebSocket) { ... } +``` + +- [ ] **Step 3: 添加 daemon 模块到 lib.rs** + +在 `crates/sandbox-core/src/lib.rs` 中添加: + +```rust +pub mod daemon; +``` + +- [ ] **Step 4: 添加依赖** + +在 `crates/sandbox-core/Cargo.toml` 中确保有: + +```toml +chrono = "0.4" +dirs-next = "2" +libc = "0.2" +tower-http = { version = "0.6", features = ["cors"] } +``` + +- [ ] **Step 5: 验证编译** + +Run: `cargo check -p sandbox-daemon` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add crates/sandbox-core/src/daemon/ crates/sandbox-core/src/lib.rs crates/sandbox-core/Cargo.toml +git commit -m "feat(daemon): implement daemon lifecycle and multi-sandbox HTTP API" +``` + +--- + +## Task 3: 验证 daemon 端口发现和生命周期 + +**Files:** +- Test: `crates/sandbox-daemon/src/main.rs` (内联测试) + +- [ ] **Step 1: 写端口发现测试** + +在 `crates/sandbox-core/src/daemon/mod.rs` 底部添加: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn find_available_port_returns_first_free() { + let port = find_available_port(15801, 15899).unwrap(); + assert!((15801..=15899).contains(&port)); + } + + #[test] + fn find_running_daemon_returns_none_when_no_file() { + let _ = std::fs::remove_file(daemon_json_path()); + assert_eq!(find_running_daemon(), None); + } + + #[test] + fn write_and_read_daemon_info_roundtrip() { + let test_path = std::env::temp_dir().join("test_daemon.json"); + let info = DaemonInfo { + port: 15999, + pid: std::process::id(), + started_at: "2026-05-30T10:00:00Z".to_string(), + }; + let json = serde_json::to_string_pretty(&info).unwrap(); + std::fs::write(&test_path, &json).unwrap(); + let read: DaemonInfo = serde_json::from_str( + &std::fs::read_to_string(&test_path).unwrap() + ).unwrap(); + assert_eq!(read.port, 15999); + let _ = std::fs::remove_file(&test_path); + } + + #[test] + fn find_running_daemon_detects_stale_pid() { + // Write a daemon.json with a PID that definitely doesn't exist + let info = DaemonInfo { + port: 15801, + pid: 9999999, + started_at: "2026-05-30T10:00:00Z".to_string(), + }; + let path = daemon_json_path(); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let json = serde_json::to_string_pretty(&info).unwrap(); + std::fs::write(&path, &json).unwrap(); + // Should return None and clean up + assert_eq!(find_running_daemon(), None); + assert!(!path.exists()); + } +} +``` + +- [ ] **Step 2: 运行测试** + +Run: `cargo test -p sandbox-core -- daemon::tests` +Expected: 全部 PASS + +- [ ] **Step 3: 手动测试 daemon 启动和关闭** + +Run: `cargo run -p sandbox-daemon` +Expected: 打印 `Sandbox daemon started on port 15801` + +在另一个终端运行: `curl http://localhost:15801/health` +Expected: `{"status":"ok","port":15801,"uptime_secs":0,"sandbox_count":0}` + +Ctrl+C 终止 daemon,检查 `~/.sandbox/daemon.json` 已被删除。 + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "test(daemon): add port discovery and lifecycle tests" +``` + +--- + +## Task 4: 重构 CLI — `sandbox start` 改为 spawn daemon + HTTP create + +**Files:** +- Modify: `crates/sandbox-cli/src/main.rs` + +- [ ] **Step 1: 添加 `cmd_start_daemon` 函数** + +在 `main.rs` 中添加新的 `cmd_start_daemon` 函数,替代原有的 `cmd_start`: + +```rust +fn cmd_start_daemon(command: Option<&str>, args: &[String]) -> Result<()> { + // 1. Check if daemon is running + let port = match sandbox_core::daemon::find_running_daemon() { + Some(port) => { + eprintln!("Sandbox daemon running on port {port}"); + port + } + None => { + // 2. Spawn daemon in background + let daemon_bin = std::env::current_exe() + .map_err(|e| anyhow::anyhow!("Cannot find current exe: {e}"))?; + // Assume sandbox-daemon is in the same directory + let daemon_path = daemon_bin.parent() + .unwrap_or(Path::new(".")) + .join("sandbox-daemon"); + + let mut child = std::process::Command::new(&daemon_path) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .map_err(|e| anyhow::anyhow!("Failed to spawn daemon: {e}"))?; + + // 3. Wait for daemon.json to appear (timeout 5s) + let start = std::time::Instant::now(); + let port = loop { + if start.elapsed() > std::time::Duration::from_secs(5) { + child.kill().ok(); + anyhow::bail!("Daemon failed to start within 5 seconds"); + } + if let Some(p) = sandbox_core::daemon::find_running_daemon() { + break p; + } + std::thread::sleep(std::time::Duration::from_millis(100)); + }; + eprintln!("Sandbox daemon started on port {port}"); + port + } + }; + + // 4. Determine mode and command + let cmd = command.unwrap_or("zsh"); + let mode = if cmd.ends_with(".app") { "app" } else { "cli" }; + + // 5. Send HTTP POST /sandbox/create + let client = reqwest::blocking::Client::new(); + let resp = client + .post(format!("http://127.0.0.1:{port}/sandbox/create")) + .json(&serde_json::json!({ + "mode": mode, + "command": cmd, + "args": args, + })) + .send() + .map_err(|e| anyhow::anyhow!("Failed to create sandbox: {e}"))?; + + if !resp.status().is_success() { + anyhow::bail!("Failed to create sandbox: {}", resp.status()); + } + + let result: serde_json::Value = resp.json() + .map_err(|e| anyhow::anyhow!("Failed to parse response: {e}"))?; + + let sandbox_id = result["sandbox_id"].as_str().unwrap_or("unknown"); + let pty_pid = result["pty_pid"].as_u64(); + + println!("Sandbox {sandbox_id} created (port {port})"); + if let Some(pid) = pty_pid { + println!("PTY pid: {pid}"); + } + + Ok(()) +} +``` + +- [ ] **Step 2: 修改 main 匹配分支** + +在 `main()` 的命令匹配中,将 `cmd_start` 调用替换为 `cmd_start_daemon`。保留原有的 `cmd_start` 函数供参考(可标记为 `#[allow(dead_code)]`),后续 Phase 2 完成后删除。 + +- [ ] **Step 3: 添加 reqwest 依赖** + +在 `crates/sandbox-cli/Cargo.toml` 中确保有: + +```toml +reqwest = { version = "0.12", features = ["blocking", "json"] } +``` + +- [ ] **Step 4: 验证编译** + +Run: `cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-cli/src/main.rs crates/sandbox-cli/Cargo.toml +git commit -m "feat(cli): sandbox start spawns daemon and creates sandbox via HTTP" +``` + +--- + +## Task 5: 重构 CLI — 操作命令适配 daemon API + +**Files:** +- Modify: `crates/sandbox-cli/src/client.rs` +- Modify: `crates/sandbox-cli/src/main.rs` + +- [ ] **Step 1: 添加通过 daemon 端口发现来定位沙箱的逻辑** + +在 `client.rs` 中添加辅助函数: + +```rust +/// Resolve sandbox port by reading daemon.json +pub fn resolve_daemon_port() -> Result { + sandbox_core::daemon::find_running_daemon() + .ok_or_else(|| anyhow::anyhow!("No sandbox daemon running. Run `sandbox start` first.")) +} + +/// Build base URL for daemon API +pub fn daemon_base_url() -> Result { + let port = resolve_daemon_port()?; + Ok(format!("http://127.0.0.1:{port}")) +} +``` + +- [ ] **Step 2: 修改 cmd_screenshot 使用 daemon API** + +```rust +pub fn cmd_screenshot(sandbox_id: &str, output: Option<&str>) -> Result<()> { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/{sandbox_id}/screenshot"); + let resp = reqwest::blocking::Client::new() + .get(&url) + .send() + .map_err(|e| anyhow::anyhow!("Request failed: {e}"))?; + + if !resp.status().is_success() { + anyhow::bail!("Screenshot failed: {}", resp.status()); + } + + let png_data = resp.bytes().map_err(|e| anyhow::anyhow!("Read failed: {e}"))?; + let output_path = output.unwrap_or("screenshot.png"); + std::fs::write(output_path, &png_data)?; + println!("Screenshot saved to {output_path} ({} bytes)", png_data.len()); + Ok(()) +} +``` + +- [ ] **Step 3: 修改 cmd_list 使用 daemon API** + +```rust +pub fn cmd_list() -> Result<()> { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/list"); + let resp = reqwest::blocking::Client::new() + .get(&url) + .send() + .map_err(|e| anyhow::anyhow!("Request failed: {e}"))?; + + let result: serde_json::Value = resp.json() + .map_err(|e| anyhow::anyhow!("Parse failed: {e}"))?; + + if let Some(sandboxes) = result["sandboxes"].as_array() { + println!("{:<10} {:<8} {:<12} {:<10} {:<12}", "ID", "KIND", "STATUS", "PTY_PID", "WINDOW_ID"); + for sb in sandboxes { + println!( + "{:<10} {:<8} {:<12} {:<10} {:<12}", + sb["id"].as_str().unwrap_or("-"), + sb["kind"].as_str().unwrap_or("-"), + sb["status"].as_str().unwrap_or("-"), + sb["pty_pid"].map(|v| v.to_string()).unwrap_or("-".into()), + sb["window_id"].map(|v| v.to_string()).unwrap_or("-".into()), + ); + } + } + Ok(()) +} +``` + +- [ ] **Step 4: 类似地修改 cmd_click, cmd_type, cmd_key, cmd_close** + +每个命令改为 `POST {base}/sandbox/{id}/input/...` 或 `POST {base}/sandbox/{id}/close`,不再需要指定端口。 + +- [ ] **Step 5: 验证编译** + +Run: `cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add crates/sandbox-cli/src/client.rs crates/sandbox-cli/src/main.rs +git commit -m "feat(cli): adapt operation commands to daemon HTTP API" +``` + +--- + +## Task 6: 端到端集成测试 + +**Files:** +- Test: Manual integration test (no new files) + +- [ ] **Step 1: 构建 daemon 和 CLI** + +Run: `cargo build -p sandbox-daemon -p sandbox-cli` +Expected: PASS + +- [ ] **Step 2: 复制 daemon 二进制到 CLI 同目录** + +```bash +cp target/debug/sandbox-daemon target/debug/ +``` + +- [ ] **Step 3: 测试 sandbox start** + +Run: `cargo run -p sandbox-cli -- start zsh` +Expected: +``` +Sandbox daemon started on port 15801 +Sandbox abc123 created (port 15801) +PTY pid: 45678 +``` + +- [ ] **Step 4: 测试 sandbox list** + +Run: `cargo run -p sandbox-cli -- list` +Expected: 列出刚创建的沙箱 + +- [ ] **Step 5: 测试 sandbox screenshot** + +Run: `cargo run -p sandbox-cli -- screenshot --id -o test.png` +Expected: `test.png` 生成 + +- [ ] **Step 6: 测试 sandbox close** + +Run: `cargo run -p sandbox-cli -- close ` +Expected: 沙箱被关闭,PTY 进程终止 + +- [ ] **Step 7: 测试端口自动递增** + +手动占用 15801 端口后再次运行 `sandbox start`,验证端口自动切到 15802。 + +- [ ] **Step 8: Commit 整体状态** + +```bash +git add -A +git commit -m "test(daemon): Phase 1 end-to-end integration verified" +``` + +--- + +## Task 7: 清理和收尾 + +**Files:** +- Modify: `CLAUDE.md` (更新架构描述) +- Modify: `crates/sandbox-core/src/server/mod.rs` (标记旧单实例路由为 deprecated) + +- [ ] **Step 1: 更新 CLAUDE.md** + +在 CLAUDE.md 中更新架构描述,反映 daemon 模式。将 "桌面框架 | Tauri 2.x" 改为 "桌面框架 | Electron (Phase 2) / Daemon HTTP API (Phase 1)"。 + +- [ ] **Step 2: 标记旧 server 路由** + +在 `server/mod.rs` 的 `build_router` 上方添加注释: + +```rust +// DEPRECATED: This router is for the legacy Tauri single-instance mode. +// Use daemon::build_daemon_router for the new multi-sandbox daemon mode. +``` + +- [ ] **Step 3: 运行完整测试套件** + +Run: `cargo test --all && cargo fmt --all -- --check && cargo clippy --all-targets` +Expected: PASS + +- [ ] **Step 4: Final commit** + +```bash +git add -A +git commit -m "chore: update docs and mark legacy server as deprecated" +``` + +--- + +## Self-Review + +### Spec coverage + +| Spec 要求 | 对应 Task | +|-----------|----------| +| Daemon 独立进程管理多沙箱 | Task 1, 2 | +| HTTP API `/sandbox/:id/...` 路由 | Task 2 | +| 端口发现 (daemon.json + pid 验证) | Task 2, 3 | +| 端口占用时自动递增 | Task 2 | +| CLI spawn daemon + HTTP create | Task 4 | +| CLI 操作命令适配 daemon API | Task 5 | +| PTY WebSocket 复用 | Task 2 (pty_ws handler) | +| Ctrl+C 优雅关闭清理 daemon.json | Task 2 | + +### Placeholder scan + +无 TBD/TODO。所有步骤包含完整代码。 + +### Type consistency + +- `DaemonState` 在 daemon/mod.rs 中定义,与 main.rs 和 router handlers 一致 +- `CreateSandboxRequest/Response`, `ListSandboxesResponse`, `SandboxSummary` 在 daemon/mod.rs 中定义 +- `ClickRequest`, `TypeRequest`, `KeyRequest`, `ScrollRequest`, `SpawnAppRequest` 从 server/mod.rs 导出为 pub,在 daemon/mod.rs 中引用 +- `ManagedSandbox` 使用 `InstanceKind`(从 instance/mod.rs 导出) + +### Gaps + +- **Electron 端未实现** — 这是 Phase 2 的范围,将在 Phase 1 完成后另写计划 +- **WebSocket 事件通知(sandbox_exit 等)** — daemon handler 中需要 broadcast channel 通知 Electron。当前 Phase 1 只需要 CLI 功能(不依赖事件通知),WebSocket 事件将在 Phase 2 实现 +- **MCP server 适配** — 当前 sandbox-cli/src/mcp_server.rs 需要适配 daemon API,但不影响核心功能,可在 Phase 2 中处理 diff --git a/docs/superpowers/plans/2026-05-30-electron-shell.md b/docs/superpowers/plans/2026-05-30-electron-shell.md new file mode 100644 index 0000000..b89444f --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-electron-shell.md @@ -0,0 +1,1342 @@ +# Phase 2: Electron Shell Implementation 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:** Replace the Tauri window with an Electron shell that renders xterm.js terminals via Chromium, eliminating WKWebView rendering artifacts. The Electron app connects to the existing sandbox-daemon via HTTP/WebSocket and manages multiple sandbox tabs in a single window. + +**Architecture:** Electron main process spawns sandbox-daemon as a child process. Each sandbox gets a WebContentsView tab containing xterm.js that connects directly to the daemon's PTY WebSocket. CLI mode sandboxes render in xterm.js using standard `term.write()` (no writeDirect hack). Tab switching positions WebContentsViews off-screen (waveterm strategy). `sandbox start` CLI command launches Electron if not running and triggers new-tab creation via daemon event. + +**Tech Stack:** Electron 34.x, TypeScript, electron-vite, React 18, @xterm/xterm 6.x, Vite 6 + +**Spec:** `docs/design/electron-rust-architecture.md` (Section 3.2, 3.3, Phase 2 in Section 9) + +**Depends on:** Phase 1 complete (`sandbox-daemon` running with HTTP API on port 15801–15899, CLI `sandbox start/type/key --pty` all working) + +--- + +## Scope + +本计划覆盖 Phase 2 的完整实现。完成后 `sandbox start claude` 会打开 Electron 窗口,xterm.js 用标准 `term.write()` 渲染 Claude Code(Chromium 引擎,无 WKWebView 问题)。 + +Phase 3(守护与恢复、心跳检测、崩溃恢复、系统托盘)将在 Phase 2 完成后另写计划。 + +## File Structure + +``` +新增/修改文件清单: + +electron-app/ # 🆕 Electron 应用 +├── package.json # 项目配置 + electron-builder +├── tsconfig.json # TypeScript 配置 +├── vite.config.ts # electron-vite 配置 +├── electron-builder.config.cjs # 打包配置 +├── src/ +│ ├── main/ +│ │ ├── index.ts # Electron 入口:requestSingleInstanceLock, spawn daemon, create window +│ │ ├── window.ts # BrowserWindow 创建和管理 +│ │ ├── tab-manager.ts # Tab (WebContentsView) 创建/切换/销毁 +│ │ ├── daemon-bridge.ts # 与 daemon 的 HTTP 通信 +│ │ └── ipc-handlers.ts # preload IPC 桥接 +│ ├── preload/ +│ │ └── index.ts # contextBridge 暴露安全 API +│ └── renderer/ +│ ├── index.html # Tab 渲染页面入口 +│ ├── main.tsx # React 入口(渲染终端/控制面板) +│ ├── api.ts # 连接 daemon 的 HTTP/WS 客户端 +│ └── components/ +│ └── Terminal.tsx # xterm.js 终端(去掉 writeDirect) +│ +sandbox-web/ # 🔧 保留但逐步迁移 +│ +crates/sandbox-cli/src/ +├── main.rs # 🔧 修改:spawn Electron 进程 +└── client.rs # ✅ 不变 + +crates/sandbox-core/src/daemon/ +└── mod.rs # 🔧 小改:添加 Electron 状态文件支持 +``` + +--- + +## Task 1: 搭建 electron-app 项目骨架 + +**Files:** +- Create: `electron-app/package.json` +- Create: `electron-app/tsconfig.json` +- Create: `electron-app/tsconfig.node.json` +- Create: `electron-app/vite.config.ts` +- Create: `electron-app/electron-builder.config.cjs` +- Create: `electron-app/src/main/index.ts` (最简 main) +- Create: `electron-app/src/preload/index.ts` +- Create: `electron-app/src/renderer/index.html` + +- [ ] **Step 1: 创建 electron-app/package.json** + +```json +{ + "name": "sandbox-electron", + "version": "0.1.0", + "private": true, + "main": "./dist/main/index.js", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview", + "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json", + "pack": "electron-builder --config electron-builder.config.cjs", + "dist": "npm run build && npm run pack" + }, + "dependencies": { + "electron-store": "^10.0.0" + }, + "devDependencies": { + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "electron": "^34.0.0", + "electron-builder": "^25.1.8", + "electron-vite": "^3.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.5", + "tailwindcss": "^3.4.17", + "postcss": "^8.4.49", + "autoprefixer": "^10.4.20" + } +} +``` + +- [ ] **Step 2: 创建 tsconfig.json** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src/renderer/**/*"] +} +``` + +- [ ] **Step 3: 创建 tsconfig.node.json** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/main/**/*", "src/preload/**/*", "vite.config.ts"] +} +``` + +- [ ] **Step 4: 创建 vite.config.ts** + +```typescript +import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin } from "electron-vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + }, + preload: { + plugins: [externalizeDepsPlugin()], + }, + renderer: { + plugins: [react()], + resolve: { + alias: { + "@": resolve("src/renderer"), + }, + }, + }, +}); +``` + +- [ ] **Step 5: 创建 electron-builder.config.cjs** + +```javascript +/** @type {import('electron-builder').Configuration} */ +const config = { + appId: "com.system-test-sandbox", + productName: "System Test Sandbox", + directories: { + output: "../../dist/electron", + }, + mac: { + target: ["dmg"], + category: "public.app-category.developer-tools", + }, + files: ["dist/**/*"], + extraResources: [ + { + from: "../../target/release/sandbox-daemon", + to: "sandbox-daemon", + }, + ], +}; + +module.exports = config; +``` + +- [ ] **Step 6: 创建 src/main/index.ts (最小 main process)** + +```typescript +import { app, BrowserWindow } from "electron"; +import { join } from "path"; + +let mainWindow: BrowserWindow | null = null; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + title: "System Test Sandbox", + titleBarStyle: "hiddenInset", + webPreferences: { + preload: join(__dirname, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + if (process.env.ELECTRON_RENDERER_URL) { + mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); + } else { + mainWindow.loadFile(join(__dirname, "../renderer/index.html")); + } +} + +app.whenReady().then(createWindow); + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") app.quit(); +}); + +app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); +}); +``` + +- [ ] **Step 7: 创建 src/preload/index.ts** + +```typescript +import { contextBridge, ipcRenderer } from "electron"; + +contextBridge.exposeInMainWorld("sandbox", { + getDaemonPort: () => ipcRenderer.invoke("get-daemon-port"), + onNewSandbox: (callback: (sandboxId: string, ptyPid: number, kind: string) => void) => { + ipcRenderer.on("new-sandbox", (_event, sandboxId, ptyPid, kind) => + callback(sandboxId, ptyPid, kind), + ); + }, +}); +``` + +- [ ] **Step 8: 创建 src/renderer/index.html** + +```html + + + + + + System Test Sandbox + + +
+ + + +``` + +- [ ] **Step 9: 创建 src/renderer/main.tsx (占位)** + +```tsx +import ReactDOM from "react-dom/client"; + +function App() { + return ( +
+

System Test Sandbox — Electron

+
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")!).render(); +``` + +- [ ] **Step 10: 安装依赖并验证 dev 启动** + +```bash +cd electron-app && pnpm install && pnpm dev +``` + +Expected: Electron 窗口打开显示 "System Test Sandbox — Electron" + +- [ ] **Step 11: Commit** + +```bash +git add electron-app/ +git commit -m "feat(electron): scaffold electron-app with electron-vite" +``` + +--- + +## Task 2: Electron main — spawn daemon + 单实例锁 + +**Files:** +- Modify: `electron-app/src/main/index.ts` +- Create: `electron-app/src/main/daemon-bridge.ts` + +- [ ] **Step 1: 创建 daemon-bridge.ts** + +负责发现和启动 sandbox-daemon。 + +```typescript +import { spawn, ChildProcess } from "child_process"; +import { readFileSync, existsSync } from "fs"; +import { join, dirname } from "path"; +import { app } from "electron"; + +let daemonProcess: ChildProcess | null = null; + +interface DaemonInfo { + port: number; + pid: number; + started_at: string; +} + +function daemonJsonPath(): string { + const home = process.env.HOME || "/tmp"; + return join(home, ".sandbox", "daemon.json"); +} + +function readDaemonInfo(): DaemonInfo | null { + const path = daemonJsonPath(); + if (!existsSync(path)) return null; + try { + return JSON.parse(readFileSync(path, "utf-8")); + } catch { + return null; + } +} + +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +export function findRunningDaemon(): number | null { + const info = readDaemonInfo(); + if (!info) return null; + if (isProcessAlive(info.pid)) return info.port; + // Stale — ignore + return null; +} + +function findDaemonBinary(): string { + // Dev mode: relative to project + const devPath = join(__dirname, "..", "..", "..", "target", "release", "sandbox-daemon"); + if (existsSync(devPath)) return devPath; + // Production: bundled in app resources + const prodPath = join(process.resourcesPath, "sandbox-daemon"); + if (existsSync(prodPath)) return prodPath; + // Same directory as electron binary + return join(dirname(app.getPath("exe")), "sandbox-daemon"); +} + +export async function ensureDaemon(): Promise { + const existingPort = findRunningDaemon(); + if (existingPort) return existingPort; + + const bin = findDaemonBinary(); + daemonProcess = spawn(bin, [], { + stdio: "pipe", + detached: false, + }); + + daemonProcess.stdout?.on("data", (data: Buffer) => { + console.log(`[daemon] ${data.toString().trim()}`); + }); + daemonProcess.stderr?.on("data", (data: Buffer) => { + console.error(`[daemon] ${data.toString().trim()}`); + }); + + // Wait for daemon.json to appear (up to 5s) + const port = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Daemon failed to start within 5s")); + }, 5000); + + const check = () => { + const info = readDaemonInfo(); + if (info && isProcessAlive(info.pid)) { + clearTimeout(timeout); + resolve(info.port); + } else { + setTimeout(check, 100); + } + }; + check(); + }); + + console.log(`Daemon started on port ${port}`); + return port; +} + +export function killDaemon() { + if (daemonProcess && !daemonProcess.killed) { + daemonProcess.kill(); + } +} +``` + +- [ ] **Step 2: 修改 main/index.ts — 加入 requestSingleInstanceLock + spawn daemon** + +```typescript +import { app, BrowserWindow, ipcMain } from "electron"; +import { join } from "path"; +import { ensureDaemon, killDaemon } from "./daemon-bridge"; + +let mainWindow: BrowserWindow | null = null; +let daemonPort: number | null = null; + +const gotTheLock = app.requestSingleInstanceLock(); + +if (!gotTheLock) { + app.quit(); +} else { + app.on("second-instance", () => { + // Someone tried to run a second instance — focus our window + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + } + }); + + app.whenReady().then(async () => { + try { + daemonPort = await ensureDaemon(); + } catch (err) { + console.error("Failed to start daemon:", err); + app.quit(); + return; + } + + createWindow(); + }); +} + +// IPC: renderer asks for daemon port +ipcMain.handle("get-daemon-port", () => daemonPort); + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + title: "System Test Sandbox", + titleBarStyle: "hiddenInset", + show: false, + webPreferences: { + preload: join(__dirname, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + if (process.env.ELECTRON_RENDERER_URL) { + mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); + } else { + mainWindow.loadFile(join(__dirname, "../renderer/index.html")); + } + + mainWindow.once("ready-to-show", () => { + mainWindow?.show(); + }); + + mainWindow.on("closed", () => { + mainWindow = null; + }); +} + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + killDaemon(); + app.quit(); + } +}); + +app.on("before-quit", () => { + killDaemon(); +}); + +app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0 && daemonPort) { + createWindow(); + } +}); +``` + +- [ ] **Step 3: 验证 Electron 启动并 spawn daemon** + +```bash +cd electron-app && pnpm dev +``` + +Expected: Electron 窗口打开,终端日志显示 `Daemon started on port 15801` +验证: `curl http://localhost:15801/health` 返回 `{"status":"ok",...}` + +- [ ] **Step 4: Commit** + +```bash +git add electron-app/src/main/ +git commit -m "feat(electron): single-instance lock + spawn sandbox-daemon" +``` + +--- + +## Task 3: Tab Manager — WebContentsView 多标签 + +**Files:** +- Create: `electron-app/src/main/tab-manager.ts` +- Modify: `electron-app/src/main/index.ts` + +- [ ] **Step 1: 创建 tab-manager.ts** + +```typescript +import { BrowserWindow, WebContentsView } from "electron"; +import { join } from "path"; + +export interface SandboxTab { + id: string; + kind: "cli" | "app"; + title: string; + webContentsView: WebContentsView; +} + +const tabs: Map = new Map(); +let activeTabId: string | null = null; +let mainWindow: BrowserWindow | null = null; + +const TAB_BAR_HEIGHT = 36; +const TITLE_BAR_HEIGHT = 28; + +export function setMainWindow(win: BrowserWindow) { + mainWindow = win; +} + +export function createTab( + sandboxId: string, + kind: "cli" | "app", + title: string, + daemonPort: number, +): SandboxTab { + if (!mainWindow) throw new Error("No main window"); + + const view = new WebContentsView({ + webPreferences: { + preload: join(__dirname, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + // Load renderer page with sandbox params + const baseUrl = process.env.ELECTRON_RENDERER_URL + ? process.env.ELECTRON_RENDERER_URL + : `file://${join(__dirname, "../renderer/index.html")}`; + + const url = new URL(baseUrl); + url.searchParams.set("sandbox_id", sandboxId); + url.searchParams.set("kind", kind); + url.searchParams.set("title", title); + url.searchParams.set("daemon_port", daemonPort.toString()); + view.webContents.loadURL(url.toString()); + + const tab: SandboxTab = { + id: sandboxId, + kind, + title, + webContentsView: view, + }; + + tabs.set(sandboxId, tab); + + // If first tab, activate immediately; otherwise position off-screen + if (tabs.size === 1) { + switchToTab(sandboxId); + } else { + positionViewOffScreen(view); + } + + mainWindow.contentView.addChildView(view); + return tab; +} + +export function switchToTab(targetId: string) { + if (!mainWindow) return; + const target = tabs.get(targetId); + if (!target) return; + + const { width, height } = mainWindow.getContentBounds(); + const topOffset = TAB_BAR_HEIGHT + TITLE_BAR_HEIGHT; + + // Move all tabs off-screen except target + for (const [id, tab] of tabs) { + if (id === targetId) { + tab.webContentsView.setBounds({ + x: 0, + y: topOffset, + width, + height: height - topOffset, + }); + } else { + positionViewOffScreen(tab.webContentsView); + } + } + + activeTabId = targetId; +} + +export function closeTab(sandboxId: string) { + const tab = tabs.get(sandboxId); + if (!tab) return; + + mainWindow?.contentView.removeChildView(tab.webContentsView); + tab.webContentsView.webContents.close(); + tabs.delete(sandboxId); + + // If closed active tab, switch to another + if (activeTabId === sandboxId) { + const remaining = Array.from(tabs.keys()); + if (remaining.length > 0) { + switchToTab(remaining[0]); + } else { + activeTabId = null; + } + } +} + +export function getActiveTabId(): string | null { + return activeTabId; +} + +export function getAllTabs(): SandboxTab[] { + return Array.from(tabs.values()); +} + +function positionViewOffScreen(view: WebContentsView) { + view.setBounds({ x: -15000, y: -15000, width: 1200, height: 800 }); +} +``` + +- [ ] **Step 2: 修改 main/index.ts — 集成 Tab Manager** + +在 `createWindow()` 之后加入 tab manager 初始化,添加 IPC handlers 用于 tab 操作: + +在 `main/index.ts` 顶部添加 import: + +```typescript +import * as tabManager from "./tab-manager"; +``` + +在 `createWindow()` 函数内,`mainWindow` 赋值后添加: + +```typescript +tabManager.setMainWindow(mainWindow); +``` + +在 `ipcMain.handle("get-daemon-port", ...)` 之后添加: + +```typescript +// IPC: renderer requests new tab +ipcMain.handle("create-tab", (_event, sandboxId: string, kind: string, title: string) => { + if (!daemonPort) throw new Error("Daemon not running"); + tabManager.createTab(sandboxId, kind, title, daemonPort); +}); + +// IPC: renderer requests tab switch +ipcMain.handle("switch-tab", (_event, sandboxId: string) => { + tabManager.switchToTab(sandboxId); +}); + +// IPC: renderer requests tab close +ipcMain.handle("close-tab", (_event, sandboxId: string) => { + tabManager.closeTab(sandboxId); +}); + +// IPC: list tabs +ipcMain.handle("list-tabs", () => { + return tabManager.getAllTabs().map((t) => ({ + id: t.id, + kind: t.kind, + title: t.title, + })); +}); +``` + +- [ ] **Step 3: 更新 preload — 暴露 tab IPC** + +更新 `src/preload/index.ts`: + +```typescript +import { contextBridge, ipcRenderer } from "electron"; + +contextBridge.exposeInMainWorld("sandbox", { + getDaemonPort: () => ipcRenderer.invoke("get-daemon-port"), + createTab: (sandboxId: string, kind: string, title: string) => + ipcRenderer.invoke("create-tab", sandboxId, kind, title), + switchTab: (sandboxId: string) => ipcRenderer.invoke("switch-tab", sandboxId), + closeTab: (sandboxId: string) => ipcRenderer.invoke("close-tab", sandboxId), + listTabs: () => ipcRenderer.invoke("list-tabs"), +}); +``` + +- [ ] **Step 4: 验证编译** + +```bash +cd electron-app && pnpm typecheck +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add electron-app/src/ +git commit -m "feat(electron): tab manager with WebContentsView + off-screen positioning" +``` + +--- + +## Task 4: Renderer — xterm.js 终端组件(标准 term.write) + +**Files:** +- Modify: `electron-app/src/renderer/main.tsx` +- Create: `electron-app/src/renderer/api.ts` +- Create: `electron-app/src/renderer/components/Terminal.tsx` +- Create: `electron-app/src/renderer/components/TabBar.tsx` + +- [ ] **Step 1: 创建 api.ts — daemon 连接层** + +```typescript +/** + * Daemon API client for Electron renderer. + * Port comes from URL param (set by tab-manager when creating the WebContentsView). + */ + +function getDaemonPort(): number { + const params = new URLSearchParams(window.location.search); + const p = params.get("daemon_port"); + return p ? Number(p) : 15801; +} + +function getSandboxId(): string { + const params = new URLSearchParams(window.location.search); + return params.get("sandbox_id") || ""; +} + +function getKind(): string { + const params = new URLSearchParams(window.location.search); + return params.get("kind") || "cli"; +} + +function getTitle(): string { + const params = new URLSearchParams(window.location.search); + return params.get("title") || ""; +} + +const PORT = getDaemonPort(); +const BASE = `http://127.0.0.1:${PORT}`; +export { PORT, getSandboxId, getKind, getTitle }; + +export interface PtyConnection { + onOutput: (cb: (data: string | Uint8Array) => void) => () => void; + sendInput: (data: string) => void; + resize: (cols: number, rows: number) => void; + close: () => void; +} + +export function connectPty(ptyPid: number): PtyConnection { + const ws = new WebSocket(`ws://127.0.0.1:${PORT}/sandbox/${getSandboxId()}/pty/ws/${ptyPid}`); + ws.binaryType = "arraybuffer"; + const outputListeners: ((data: string | Uint8Array) => void)[] = []; + + ws.onmessage = (e) => { + if (e.data instanceof ArrayBuffer) { + for (const cb of outputListeners) cb(new Uint8Array(e.data)); + } else if (typeof e.data === "string") { + for (const cb of outputListeners) cb(e.data); + } + }; + + return { + onOutput(cb) { + outputListeners.push(cb); + return () => { + const idx = outputListeners.indexOf(cb); + if (idx >= 0) outputListeners.splice(idx, 1); + }; + }, + sendInput(data) { + if (ws.readyState === WebSocket.OPEN) ws.send(data); + }, + resize(cols, rows) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: "resize", cols, rows })); + } + }, + close() { + ws.close(); + }, + }; +} + +export async function fetchSandboxInfo(): Promise<{ + id: string; + kind: { type: string; detail: { command: string; args: string[] } }; + status: { type: string }; + pty_pid: number | null; +}> { + const res = await fetch(`${BASE}/sandbox/list`); + const list = await res.json(); + return list.find((sb: { id: string }) => sb.id === getSandboxId()); +} +``` + +- [ ] **Step 2: 创建 Terminal.tsx — 标准 term.write(),无 writeDirect** + +```tsx +import { useEffect, useRef, useCallback } from "react"; +import { Terminal } from "@xterm/xterm"; +import { FitAddon } from "@xterm/addon-fit"; +import { connectPty } from "../api"; +import "@xterm/xterm/css/xterm.css"; + +interface TerminalProps { + ptyPid: number; + onReady?: (cols: number, rows: number) => void; +} + +export default function SandboxTerminal({ ptyPid, onReady }: TerminalProps) { + const terminalRef = useRef(null); + const xtermRef = useRef(null); + const fitAddonRef = useRef(null); + const connRef = useRef | null>(null); + + // Initialize xterm.js + useEffect(() => { + if (!terminalRef.current) return; + if (xtermRef.current) return; + + const term = new Terminal({ + cursorBlink: true, + cursorStyle: "bar", + fontSize: 14, + fontFamily: '"SF Mono", "Menlo", "Monaco", monospace', + fontWeight: "400", + fontWeightBold: "600", + scrollback: 10000, + theme: { + background: "#1a1b26", + foreground: "#a9b1d6", + cursor: "#c0caf5", + selectionBackground: "#33467c", + }, + }); + + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.open(terminalRef.current); + fitAddon.fit(); + + onReady?.(term.cols, term.rows); + + term.onData((data) => { + connRef.current?.sendInput(data); + }); + + const handleResize = () => { + fitAddon.fit(); + connRef.current?.resize(term.cols, term.rows); + }; + window.addEventListener("resize", handleResize); + + xtermRef.current = term; + fitAddonRef.current = fitAddon; + + return () => { + window.removeEventListener("resize", handleResize); + term.dispose(); + xtermRef.current = null; + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Connect to PTY WebSocket + useEffect(() => { + connRef.current?.close(); + connRef.current = null; + + const conn = connectPty(ptyPid); + connRef.current = conn; + + const decoder = new TextDecoder(); + conn.onOutput((data) => { + const term = xtermRef.current; + if (!term) return; + const writeData = typeof data === "string" ? data : decoder.decode(data as Uint8Array); + // Standard term.write() — Chromium handles rendering correctly + term.write(writeData); + }); + + // Send initial resize + const term = xtermRef.current; + if (term) { + conn.resize(term.cols, term.rows); + } + + return () => { + conn.close(); + connRef.current = null; + }; + }, [ptyPid]); + + const containerRef = useCallback((node: HTMLDivElement | null) => { + if (node) { + requestAnimationFrame(() => fitAddonRef.current?.fit()); + } + }, []); + + return ( +
+
+
+ ); +} +``` + +- [ ] **Step 3: 创建 TabBar.tsx** + +```tsx +import { getAllTabs, switchTab, closeTab } from "../api"; + +interface TabBarProps { + activeTabId: string | null; + onRefresh?: () => void; +} + +export default function TabBar({ activeTabId, onRefresh }: TabBarProps) { + // Tab list comes from Electron IPC via preload + // For now, use a simple implementation + return ( +
+ {/* Tabs will be populated from main process */} +
+ +
+ ); +} +``` + +- [ ] **Step 4: 更新 renderer/main.tsx** + +```tsx +import { useState, useEffect } from "react"; +import ReactDOM from "react-dom/client"; +import SandboxTerminal from "./components/Terminal"; +import { getSandboxId, getKind, getTitle, fetchSandboxInfo } from "./api"; + +declare global { + interface Window { + sandbox: { + getDaemonPort: () => Promise; + createTab: (sandboxId: string, kind: string, title: string) => Promise; + switchTab: (sandboxId: string) => Promise; + closeTab: (sandboxId: string) => Promise; + listTabs: () => Promise<{ id: string; kind: string; title: string }[]>; + }; + } +} + +function App() { + const sandboxId = getSandboxId(); + const kind = getKind(); + const title = getTitle(); + const [ptyPid, setPtyPid] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!sandboxId) { + setError("No sandbox_id provided"); + return; + } + + fetchSandboxInfo() + .then((info) => { + if (info?.pty_pid) { + setPtyPid(info.pty_pid); + } else { + setError("Sandbox has no PTY process"); + } + }) + .catch((err) => { + setError(`Failed to fetch sandbox info: ${err}`); + }); + }, [sandboxId]); + + if (error) { + return ( +
+

{error}

+
+ ); + } + + if (!ptyPid) { + return ( +
+

Connecting to sandbox...

+
+ ); + } + + return ( +
+ +
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")!).render(); +``` + +- [ ] **Step 5: 验证编译** + +```bash +cd electron-app && pnpm typecheck +``` + +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add electron-app/src/renderer/ +git commit -m "feat(electron): renderer with xterm.js terminal using standard term.write()" +``` + +--- + +## Task 5: CLI 集成 — `sandbox start` 启动 Electron + 创建 Tab + +**Files:** +- Modify: `crates/sandbox-cli/src/main.rs` (添加 Electron spawn 逻辑) + +- [ ] **Step 1: 添加 find_electron_binary 和 spawn 逻辑** + +在 `crates/sandbox-cli/src/main.rs` 中添加: + +```rust +/// Locate the Electron app binary next to the current executable. +fn find_electron_binary() -> anyhow::Result { + let exe_path = std::env::current_exe().context("Failed to get current exe path")?; + let exe_dir = exe_path.parent().context("No parent dir for exe")?; + + // Check for Electron binary in release directory + let electron_name = "System Test Sandbox"; + let app_bundle = exe_dir.join(format!("{electron_name}.app")); + if app_bundle.exists() { + return Ok(app_bundle.join("Contents/MacOS/system-test-sandbox")); + } + + // Dev mode: check electron-app/dist + let cwd = std::env::current_dir().unwrap_or_default(); + let dev_bundle = cwd.join("dist/electron/mac-arm64/System Test Sandbox.app"); + if dev_bundle.exists() { + return Ok(dev_bundle.join("Contents/MacOS/system-test-sandbox")); + } + + anyhow::bail!("Electron app not found. Build it first: cd electron-app && pnpm build && pnpm pack") +} + +/// Check if Electron is already running by reading ~/.sandbox/electron.json +fn find_running_electron() -> Option { + let path = dirs_next::home_dir()?.join(".sandbox").join("electron.json"); + if !path.exists() { + return None; + } + let json = std::fs::read_to_string(&path).ok()?; + let info: serde_json::Value = serde_json::from_str(&json).ok()?; + let pid = info["pid"].as_u64()? as i32; + unsafe { + if libc::kill(pid, 0) == 0 { + return Some(info["port"].as_u64()? as u16); + } + } + let _ = std::fs::remove_file(&path); + None +} +``` + +- [ ] **Step 2: 修改 cmd_start_daemon — 在创建沙箱后 spawn Electron** + +在 `cmd_start_daemon` 中,sandbox 创建成功后添加 Electron spawn 逻辑: + +在 `println!("Daemon port: {port}");` 之后添加: + +```rust + // Ensure Electron is running + if find_running_electron().is_none() { + if let Ok(electron_bin) = find_electron_binary() { + tracing::info!("[start] spawning Electron: {}", electron_bin.display()); + let _child = Command::new(&electron_bin) + .spawn() + .context("Failed to launch Electron app")?; + tracing::info!("[start] Electron launched"); + } else { + tracing::warn!("[start] Electron app not found, running in headless daemon mode"); + } + } +``` + +- [ ] **Step 3: 验证编译** + +```bash +cargo check -p sandbox-cli +``` + +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-cli/src/main.rs +git commit -m "feat(cli): spawn Electron app when running sandbox start" +``` + +--- + +## Task 6: second-instance 处理 — CLI 通知已有 Electron 创建新 Tab + +**Files:** +- Modify: `electron-app/src/main/index.ts` (处理 daemon 事件,自动创建 Tab) + +- [ ] **Step 1: 在 main/index.ts 中添加自动 Tab 创建** + +在 `app.on("second-instance", ...)` 回调中,除了 focus 窗口外,还需要检查 CLI 是否创建了新沙箱: + +```typescript +app.on("second-instance", async () => { + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + } + + // Poll daemon for new sandboxes and create tabs for them + // The CLI has already created the sandbox via daemon HTTP API + // We just need to discover it and create a tab + if (daemonPort) { + try { + const resp = await fetch(`http://127.0.0.1:${daemonPort}/sandbox/list`); + const sandboxes = await resp.json(); + const existingTabs = new Set(tabManager.getAllTabs().map((t) => t.id)); + for (const sb of sandboxes) { + if (!existingTabs.has(sb.id)) { + const title = sb.kind?.detail?.command || sb.id; + tabManager.createTab(sb.id, sb.kind?.type || "cli", title, daemonPort); + tabManager.switchToTab(sb.id); + } + } + } catch (err) { + console.error("Failed to sync sandboxes:", err); + } + } +}); +``` + +同时,在首次 `createWindow()` 之后也同步已有沙箱: + +在 `createWindow();` 之后添加: + +```typescript + // Sync existing sandboxes from daemon (e.g., daemon was already running) + if (daemonPort) { + try { + const resp = await fetch(`http://127.0.0.1:${daemonPort}/sandbox/list`); + const sandboxes = await resp.json(); + for (const sb of sandboxes) { + const title = sb.kind?.detail?.command || sb.id; + tabManager.createTab(sb.id, sb.kind?.type || "cli", title, daemonPort); + } + // Activate first tab + const tabs = tabManager.getAllTabs(); + if (tabs.length > 0) { + tabManager.switchToTab(tabs[0].id); + } + } catch (err) { + console.error("Failed to sync sandboxes:", err); + } + } +``` + +- [ ] **Step 2: 验证 second-instance 场景** + +手动测试流程: +1. 运行 `pnpm dev`,Electron 窗口打开 +2. 在另一个终端运行 `./release/sandbox start zsh` +3. Expected: Electron 窗口获得焦点,出现新的 zsh Tab +4. 再运行 `./release/sandbox start claude` +5. Expected: Electron 窗口获得焦点,出现新的 Claude Tab + +- [ ] **Step 3: Commit** + +```bash +git add electron-app/src/main/index.ts +git commit -m "feat(electron): auto-create tabs from daemon sandbox list on second-instance" +``` + +--- + +## Task 7: 端到端集成测试 + +**Files:** +- No new files — manual integration test + +- [ ] **Step 1: 构建全部组件** + +```bash +# Build daemon + CLI (release) +cargo build --release -p sandbox-daemon -p sandbox-cli +cp target/release/sandbox release/sandbox +cp target/release/sandbox-daemon release/sandbox-daemon +chmod +x release/sandbox release/sandbox-daemon +codesign --force --sign - release/sandbox release/sandbox-daemon + +# Build Electron app (dev mode) +cd electron-app && pnpm dev +``` + +- [ ] **Step 2: 测试场景一 — CLI 启动 zsh** + +在另一个终端运行: +```bash +./release/sandbox start zsh +``` + +Expected: +- Electron 窗口打开(或已有窗口获得焦点) +- 新 Tab 出现,xterm.js 渲染 zsh 提示符 +- 可以在终端中输入命令 + +- [ ] **Step 3: 测试场景二 — CLI 启动 Claude Code** + +```bash +./release/sandbox start claude +``` + +Expected: +- 新 Tab 出现 +- Claude Code 信任提示正确渲染(无 WKWebView 残留) +- 确认后进入 Claude 主界面 +- 标准 `term.write()` 渲染,无 writeDirect + +- [ ] **Step 4: 测试场景三 — 多 Tab 切换** + +```bash +# 已经有 2 个 Tab +./release/sandbox list +# 记录两个 sandbox ID + +# CLI 截图 +./release/sandbox screenshot --id -o test.png +``` + +Expected: 截图功能正常(通过 daemon 的 ScreenCaptureKit) + +- [ ] **Step 5: 测试场景四 — 关闭 Tab** + +```bash +./release/sandbox close +``` + +Expected: Electron 窗口中对应 Tab 被关闭,其他 Tab 不受影响 + +- [ ] **Step 6: 保存测试报告** + +将测试结果保存到 `release_test/` 目录 + +- [ ] **Step 7: Commit** + +```bash +git add -A +git commit -m "test(electron): Phase 2 end-to-end integration verified" +``` + +--- + +## Self-Review + +### Spec coverage + +| Spec 要求 (Phase 2) | 对应 Task | +|---------------------|----------| +| 搭建 electron-app 项目 (electron-vite) | Task 1 | +| requestSingleInstanceLock | Task 2 | +| spawn daemon 子进程 | Task 2 | +| Tab Manager: WebContentsView 管理 | Task 3 | +| Tab 切换 (off-screen 定位) | Task 3 | +| 前端连接 daemon (api.ts) | Task 4 | +| xterm.js 标准 term.write() (去掉 writeDirect) | Task 4 | +| CLI `sandbox start` spawn Electron | Task 5 | +| second-instance 处理(已有实例时创建新 Tab) | Task 6 | +| 端到端验证 | Task 7 | + +### Placeholder scan + +无 TBD/TODO。所有步骤包含完整代码。 + +### Type consistency + +- `SandboxTab` 接口在 `tab-manager.ts` 中定义,id/kind/title/webContentsView 字段一致 +- `api.ts` 中 `connectPty()` 返回 `PtyConnection`,与 `Terminal.tsx` 使用方式一致 +- `fetchSandboxInfo()` 返回的 JSON 结构与 daemon `GET /sandbox/list` 的响应结构一致 +- `window.sandbox` 类型声明在 `main.tsx` 中定义,与 preload 暴露的方法签名一致 + +### Gaps + +- **APP 模式控制面板**:设计文档中提到 APP 模式需要控制面板 Tab(截图预览+操作按钮)。当前 Plan 的 renderer 根据 kind 参数渲染不同组件,APP 模式的具体 UI 可在后续 task 中添加 +- **electron.json 状态文件**:设计中 Electron 写入 `~/.sandbox/electron.json` 供 CLI 发现。当前 Task 5 中 `find_running_electron()` 引用了它但未实现写入逻辑,需要在 Task 2 的 main process 中补充 +- **Tab 栏 UI**:当前 TabBar.tsx 是占位组件,实际 Tab 标签列表需要从 main process 通过 IPC 获取 diff --git a/docs/superpowers/plans/2026-05-31-phase3-ui-inspect.md b/docs/superpowers/plans/2026-05-31-phase3-ui-inspect.md new file mode 100644 index 0000000..336611c --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase3-ui-inspect.md @@ -0,0 +1,241 @@ +# Phase 3: AXUIElement UI Inspection — Implementation 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:** Expose AXUIElement UI inspection (inspect_window, find_elements, get_element_value) through daemon HTTP endpoints and CLI commands so agents can read sandbox UI structure. + +**Architecture:** The `UiInspector` functions already exist in `sandbox-core/src/automation/ax_ui.rs`. We wire them through the daemon's HTTP routes and add CLI commands + MCP tools. + +**Tech Stack:** Rust, axum, clap, serde + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `crates/sandbox-core/src/daemon/mod.rs` | Modify | Add `/sandbox/{id}/ui/inspect`, `/sandbox/{id}/ui/find`, `/sandbox/{id}/ui/value` routes | +| `crates/sandbox-cli/src/main.rs` | Modify | Add `UiInspect`, `UiFind`, `UiValue` CLI commands | +| `crates/sandbox-cli/src/client.rs` | Modify | Add `ui_inspect()`, `ui_find()`, `ui_value()` client methods | + +--- + +### Task 1: Add UI inspection HTTP endpoints to daemon + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` + +- [ ] **Step 1: Add request/response types** + +Add after the existing request types (around line 80): + +```rust +#[derive(Deserialize)] +pub struct UiFindRequest { + pub role: String, + #[serde(default)] + pub title: Option, +} + +#[derive(Serialize)] +pub struct UiValueResponse { + pub value: Option, +} +``` + +- [ ] **Step 2: Add route registrations** + +Add after the existing `.route(...)` calls (around line 207): + +```rust +.route("/sandbox/{id}/ui/inspect", get(ui_inspect_handler)) +.route("/sandbox/{id}/ui/find", post(ui_find_handler)) +.route("/sandbox/{id}/ui/value", get(ui_value_handler)) +``` + +- [ ] **Step 3: Implement handler functions** + +Add after the existing handler functions: + +```rust +async fn ui_inspect_handler( + State(state): State>>, + Path(id): Path, +) -> Result, AppError> { + let state = state.lock().await; + let sandbox = state.sandboxes.get(&id) + .ok_or_else(|| AppError::BadRequest(format!("Sandbox not found: {id}")))?; + let window_id = sandbox.window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))?; + let element = crate::automation::ax_ui::UiInspector::inspect_window(window_id)?; + Ok(Json(element)) +} + +async fn ui_find_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result>, AppError> { + let state = state.lock().await; + let sandbox = state.sandboxes.get(&id) + .ok_or_else(|| AppError::BadRequest(format!("Sandbox not found: {id}")))?; + let window_id = sandbox.window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))?; + let elements = crate::automation::ax_ui::UiInspector::find_elements( + window_id, &req.role, req.title.as_deref() + )?; + Ok(Json(elements)) +} + +async fn ui_value_handler( + State(state): State>>, + Path(id): Path, + Query(params): Query>, +) -> Result, AppError> { + let _state = state.lock().await; + let element_id = params.get("element_id") + .ok_or_else(|| AppError::BadRequest("Missing element_id".into()))?; + let value = crate::automation::ax_ui::UiInspector::get_element_value(element_id)?; + Ok(Json(UiValueResponse { value })) +} +``` + +- [ ] **Step 4: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS (no errors) + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "feat(daemon): add UI inspection HTTP endpoints" +``` + +--- + +### Task 2: Add UI inspection CLI commands + +**Files:** +- Modify: `crates/sandbox-cli/src/main.rs` +- Modify: `crates/sandbox-cli/src/client.rs` + +- [ ] **Step 1: Add client methods** + +Add to `crates/sandbox-cli/src/client.rs` after existing `daemon_*` functions, following the same pattern: + +```rust +pub async fn daemon_ui_inspect(sandbox_id: &str) -> Result { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/{sandbox_id}/ui/inspect"); + let resp = reqwest::Client::new().get(&url).send().await?.error_for_status()?; + Ok(resp.json().await?) +} + +pub async fn daemon_ui_find(sandbox_id: &str, role: &str, title: Option<&str>) -> Result { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/{sandbox_id}/ui/find"); + let mut body = serde_json::json!({ "role": role }); + if let Some(t) = title { + body["title"] = serde_json::json!(t); + } + let resp = reqwest::Client::new().post(&url).json(&body).send().await?.error_for_status()?; + Ok(resp.json().await?) +} + +pub async fn daemon_ui_value(sandbox_id: &str, element_id: &str) -> Result { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/{sandbox_id}/ui/value?element_id={element_id}"); + let resp = reqwest::Client::new().get(&url).send().await?.error_for_status()?; + Ok(resp.json().await?) +} +``` + +- [ ] **Step 2: Add CLI command variants** + +Add to the `Commands` enum in `main.rs`: + +```rust +/// Inspect UI tree of a sandbox window +UiInspect { + /// Sandbox ID + #[arg(long)] + id: String, +}, +/// Find UI elements by role/title +UiFind { + /// Sandbox ID + #[arg(long)] + id: String, + /// AX role (e.g., AXButton, AXTextField) + #[arg(long)] + role: String, + /// Optional title filter + #[arg(long)] + title: Option, +}, +/// Get value of a UI element +UiValue { + /// Sandbox ID + #[arg(long)] + id: String, + /// Element ID + #[arg(long)] + element_id: String, +}, +``` + +- [ ] **Step 3: Add command handlers** + +Add match arms in the command dispatch: + +```rust +Commands::UiInspect { id } => { + let tree = client::daemon_ui_inspect(&id).await?; + println!("{}", serde_json::to_string_pretty(&tree)?); +} +Commands::UiFind { id, role, title } => { + let elements = client::daemon_ui_find(&id, &role, title.as_deref()).await?; + println!("{}", serde_json::to_string_pretty(&elements)?); +} +Commands::UiValue { id, element_id } => { + let value = client::daemon_ui_value(&id, &element_id).await?; + println!("{}", serde_json::to_string_pretty(&value)?); +} +``` + +- [ ] **Step 4: Verify compilation** + +Run: `cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-cli/src/main.rs crates/sandbox-cli/src/client.rs +git commit -m "feat(cli): add ui-inspect, ui-find, ui-value commands" +``` + +--- + +### Task 3: Manual verification + +- [ ] **Step 1: Build and run** + +Run: `cargo build -p sandbox-cli && cargo build -p sandbox-daemon` + +- [ ] **Step 2: Start a sandbox** + +Run: `cargo run -p sandbox-cli -- start zsh` + +- [ ] **Step 3: Test UI inspect** + +Run: `cargo run -p sandbox-cli -- ui-inspect --id ` +Expected: JSON tree of AX elements (or error if no window_id — expected for CLI sandboxes) + +- [ ] **Step 4: Commit final state** + +```bash +git add -A +git commit -m "feat(phase3): UI inspection endpoints and CLI commands" +``` diff --git a/docs/superpowers/plans/2026-05-31-phase4-advanced.md b/docs/superpowers/plans/2026-05-31-phase4-advanced.md new file mode 100644 index 0000000..13af737 --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase4-advanced.md @@ -0,0 +1,445 @@ +# Phase 4: Advanced Features — Implementation 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:** Add recording/playback of sandbox operations and screenshot diff capability for automated testing workflows. + +**Architecture:** Recording captures every CLI action (type, key, click, screenshot) as a JSONL file. Playback replays the sequence with timing. Screenshot diff uses the `image` crate to compare two PNGs pixel-by-pixel. + +**Tech Stack:** Rust, serde_json, image crate, chrono + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `crates/sandbox-core/src/recorder.rs` | Create | JSONL recorder — writes actions to file | +| `crates/sandbox-core/src/player.rs` | Create | JSONL player — reads and replays actions | +| `crates/sandbox-core/src/diff.rs` | Create | Screenshot diff — pixel comparison | +| `crates/sandbox-core/src/lib.rs` | Modify | Add `pub mod recorder; pub mod player; pub mod diff;` | +| `crates/sandbox-cli/src/main.rs` | Modify | Add `Record`, `Playback`, `Diff` commands | +| `crates/sandbox-cli/src/client.rs` | Modify | Add recording client methods | + +--- + +### Task 1: Action types and recorder + +**Files:** +- Create: `crates/sandbox-core/src/recorder.rs` +- Modify: `crates/sandbox-core/src/lib.rs` + +- [ ] **Step 1: Create the recorder module** + +```rust +// crates/sandbox-core/src/recorder.rs +use crate::error::Result; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; +use std::time::Instant; + +/// A recorded action with timing information. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RecordedAction { + /// Milliseconds since recording started + pub offset_ms: u64, + /// The action type + pub action: ActionType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum ActionType { + #[serde(rename = "type")] + Type { text: String, pty: bool }, + #[serde(rename = "key")] + Key { key: String, modifiers: Vec, pty: bool }, + #[serde(rename = "click")] + Click { x: f64, y: f64, button: String }, + #[serde(rename = "screenshot")] + Screenshot { path: String }, + #[serde(rename = "wait")] + Wait { ms: u64 }, +} + +/// Records actions to a JSONL file. +pub struct Recorder { + writer: BufWriter, + start: Instant, +} + +impl Recorder { + pub fn start(path: &PathBuf) -> Result { + let file = File::create(path)?; + Ok(Self { + writer: BufWriter::new(file), + start: Instant::now(), + }) + } + + pub fn record(&mut self, action: ActionType) -> Result<()> { + let offset_ms = self.start.elapsed().as_millis() as u64; + let entry = RecordedAction { offset_ms, action }; + serde_json::to_writer(&mut self.writer, &entry)?; + self.writer.write_all(b"\n")?; + self.writer.flush()?; + Ok(()) + } +} +``` + +- [ ] **Step 2: Register module in lib.rs** + +Add to `crates/sandbox-core/src/lib.rs`: + +```rust +pub mod recorder; +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/recorder.rs crates/sandbox-core/src/lib.rs +git commit -m "feat(core): add JSONL action recorder" +``` + +--- + +### Task 2: Player + +**Files:** +- Create: `crates/sandbox-core/src/player.rs` +- Modify: `crates/sandbox-core/src/lib.rs` + +- [ ] **Step 1: Create the player module** + +```rust +// crates/sandbox-core/src/player.rs +use crate::error::Result; +use crate::recorder::RecordedAction; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::time::Duration; +use tokio::time::sleep; +use tracing::info; + +/// Callback invoked for each action during playback. +pub type ActionCallback = Box std::pin::Pin> + Send>> + Send + Sync>; + +/// Replays actions from a JSONL file. +pub struct Player; + +impl Player { + pub async fn play(path: &PathBuf, callback: &ActionCallback) -> Result<()> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut last_offset: u64 = 0; + + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { continue; } + let action: RecordedAction = serde_json::from_str(&line)?; + + // Wait for the appropriate delay + let delay = action.offset_ms.saturating_sub(last_offset); + if delay > 0 { + sleep(Duration::from_millis(delay)).await; + } + last_offset = action.offset_ms; + + info!("Playing action at {}ms: {:?}", action.offset_ms, action.action); + callback(action).await?; + } + Ok(()) + } + + pub fn load_actions(path: &PathBuf) -> Result> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut actions = Vec::new(); + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { continue; } + actions.push(serde_json::from_str(&line)?); + } + Ok(actions) + } +} +``` + +- [ ] **Step 2: Register module in lib.rs** + +Add to `crates/sandbox-core/src/lib.rs`: + +```rust +pub mod player; +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/player.rs crates/sandbox-core/src/lib.rs +git commit -m "feat(core): add JSONL action player" +``` + +--- + +### Task 3: Screenshot diff + +**Files:** +- Create: `crates/sandbox-core/src/diff.rs` +- Modify: `crates/sandbox-core/src/lib.rs` + +- [ ] **Step 1: Create the diff module** + +```rust +// crates/sandbox-core/src/diff.rs +use crate::error::{AppError, Result}; +use image::{GenericImageView, Rgba}; +use serde::Serialize; + +/// Result of comparing two screenshots. +#[derive(Debug, Serialize)] +pub struct DiffResult { + pub total_pixels: u32, + pub different_pixels: u32, + pub diff_percentage: f64, + pub diff_image: Option>, +} + +/// Compare two PNG images pixel-by-pixel. +pub fn diff_images(img_a: &[u8], img_b: &[u8], threshold: u8) -> Result { + let a = image::load_from_memory(img_a) + .map_err(|e| AppError::Screenshot(format!("Failed to load image A: {e}")))?; + let b = image::load_from_memory(img_b) + .map_err(|e| AppError::Screenshot(format!("Failed to load image B: {e}")))?; + + if a.dimensions() != b.dimensions() { + return Err(AppError::BadRequest(format!( + "Image dimensions differ: {:?} vs {:?}", + a.dimensions(), b.dimensions() + ))); + } + + let (width, height) = a.dimensions(); + let total = width * height; + let mut different: u32 = 0; + let mut diff_buf = image::RgbaImage::new(width, height); + + for y in 0..height { + for x in 0..width { + let pa: Rgba = a.get_pixel(x, y); + let pb: Rgba = b.get_pixel(x, y); + let dr = pa[0].abs_diff(pb[0]); + let dg = pa[1].abs_diff(pb[1]); + let db = pa[2].abs_diff(pb[2]); + if dr > threshold || dg > threshold || db > threshold { + different += 1; + diff_buf.put_pixel(x, y, Rgba([255, 0, 0, 255])); + } else { + diff_buf.put_pixel(x, y, pa); + } + } + } + + let mut diff_png = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut diff_png); + use image::ImageEncoder; + encoder.write_image( + diff_buf.as_raw(), + width, + height, + image::ExtendedColorType::Rgba8, + ).map_err(|e| AppError::Screenshot(format!("Failed to encode diff: {e}")))?; + + Ok(DiffResult { + total_pixels: total, + different_pixels: different, + diff_percentage: (different as f64 / total as f64) * 100.0, + diff_image: Some(diff_png), + }) +} +``` + +- [ ] **Step 2: Register module in lib.rs** + +```rust +pub mod diff; +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/diff.rs crates/sandbox-core/src/lib.rs +git commit -m "feat(core): add screenshot pixel diff" +``` + +--- + +### Task 4: CLI commands for record/playback/diff + +**Files:** +- Modify: `crates/sandbox-cli/src/main.rs` + +- [ ] **Step 1: Add CLI commands** + +Add to the `Commands` enum: + +```rust +/// Record sandbox actions to a JSONL file +Record { + /// Sandbox ID + #[arg(long)] + id: String, + /// Output file path + #[arg(long, short)] + output: PathBuf, +}, +/// Replay actions from a JSONL file +Playback { + /// Sandbox ID + #[arg(long)] + id: String, + /// JSONL file to replay + #[arg(long, short)] + input: PathBuf, + /// Speed multiplier (1.0 = real-time) + #[arg(long, default_value = "1.0")] + speed: f64, +}, +/// Compare two screenshots +Diff { + /// First screenshot path + #[arg(long)] + a: PathBuf, + /// Second screenshot path + #[arg(long)] + b: PathBuf, + /// Pixel difference threshold (0-255) + #[arg(long, default_value = "10")] + threshold: u8, + /// Output diff image path + #[arg(long, short)] + output: Option, +}, +``` + +- [ ] **Step 2: Implement Diff command handler** + +```rust +Commands::Diff { a, b, threshold, output } => { + let img_a = std::fs::read(&a)?; + let img_b = std::fs::read(&b)?; + let result = sandbox_core::diff::diff_images(&img_a, &img_b, threshold)?; + println!("Total pixels: {}", result.total_pixels); + println!("Different: {} ({:.2}%)", result.different_pixels, result.diff_percentage); + if let (Some(out_path), Some(img)) = (&output, &result.diff_image) { + std::fs::write(out_path, img)?; + println!("Diff image saved to: {}", out_path.display()); + } +} +``` + +- [ ] **Step 3: Implement Record/Playback stubs** + +For now, Record and Playback require the daemon HTTP endpoints (added in a later step). Add stub handlers that print a message: + +```rust +Commands::Record { id, output } => { + println!("Recording sandbox {id} to {}...", output.display()); + println!("Use 'sandbox type', 'sandbox key', 'sandbox click' commands while recording."); + println!("Recording is integrated into the daemon — use HTTP API for now."); +} +Commands::Playback { id, input, speed } => { + println!("Playing back {} on sandbox {id} at {speed}x speed...", input.display()); + println!("Use daemon HTTP API for playback execution."); +} +``` + +- [ ] **Step 4: Verify compilation** + +Run: `cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-cli/src/main.rs +git commit -m "feat(cli): add diff command and record/playback stubs" +``` + +--- + +### Task 5: Unit tests + +**Files:** +- Create: `crates/sandbox-core/tests/diff_test.rs` + +- [ ] **Step 1: Write diff test** + +```rust +// crates/sandbox-core/tests/diff_test.rs +use sandbox_core::diff::diff_images; + +#[test] +fn test_identical_images_return_zero_diff() { + // Create a 2x2 red PNG + let img = image::RgbaImage::from_pixel(2, 2, image::Rgba([255, 0, 0, 255])); + let mut buf = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut buf); + use image::ImageEncoder; + encoder.write_image(img.as_raw(), 2, 2, image::ExtendedColorType::Rgba8).unwrap(); + + let result = diff_images(&buf, &buf, 10).unwrap(); + assert_eq!(result.different_pixels, 0); + assert_eq!(result.diff_percentage, 0.0); +} + +#[test] +fn test_different_images_detect_changes() { + let red = image::RgbaImage::from_pixel(2, 2, image::Rgba([255, 0, 0, 255])); + let blue = image::RgbaImage::from_pixel(2, 2, image::Rgba([0, 0, 255, 255])); + + let encode = |img: &image::RgbaImage| -> Vec { + let mut buf = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut buf); + use image::ImageEncoder; + encoder.write_image(img.as_raw(), 2, 2, image::ExtendedColorType::Rgba8).unwrap(); + buf + }; + + let result = diff_images(&encode(&red), &encode(&blue), 10).unwrap(); + assert_eq!(result.different_pixels, 4); + assert!((result.diff_percentage - 100.0).abs() < 0.01); +} +``` + +- [ ] **Step 2: Run tests** + +Run: `cargo test -p sandbox-core --test diff_test` +Expected: PASS + +- [ ] **Step 3: Commit** + +```bash +git add crates/sandbox-core/tests/diff_test.rs +git commit -m "test(core): add screenshot diff unit tests" +``` diff --git a/docs/superpowers/plans/2026-05-31-phase5-multi-instance.md b/docs/superpowers/plans/2026-05-31-phase5-multi-instance.md new file mode 100644 index 0000000..06a7736 --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase5-multi-instance.md @@ -0,0 +1,241 @@ +# Phase 5: Multi-Instance Management — Implementation 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:** Harden the multi-sandbox management system — stale instance cleanup, window_id propagation from Electron to daemon, instance status lifecycle, and concurrent safety. + +**Architecture:** The daemon already manages multiple sandboxes via `DaemonState.sandboxes`. This phase adds: (1) periodic cleanup of dead instances, (2) a `POST /sandbox/{id}/window` endpoint for Electron to report window IDs, (3) proper status transitions (Starting → Running → Stopped). + +**Tech Stack:** Rust, tokio, axum + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `crates/sandbox-core/src/daemon/mod.rs` | Modify | Add cleanup task, window_id endpoint, status transitions | +| `crates/sandbox-cli/src/main.rs` | Modify | Report window_id after Electron connects | +| `crates/sandbox-cli/src/client.rs` | Modify | Add `set_window_id()` client method | + +--- + +### Task 1: Stale instance cleanup + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` + +- [ ] **Step 1: Add cleanup function** + +Add after the existing `DaemonState` impl: + +```rust +impl DaemonState { + /// Remove sandboxes whose PTY process is no longer running. + pub fn cleanup_dead_sandboxes(&mut self) -> Vec { + let mut removed = Vec::new(); + self.sandboxes.retain(|id, sb| { + if let Some(pty_pid) = sb.pty_pid { + // Check if process is still alive + let alive = unsafe { libc::kill(pty_pid as i32, 0) == 0 }; + if !alive { + tracing::info!("Cleaning up dead sandbox {id} (pty_pid={pty_pid})"); + removed.push(id.clone()); + return false; + } + } + true + }); + removed + } +} +``` + +- [ ] **Step 2: Spawn cleanup task in run_daemon** + +Add inside `run_daemon()` after the server starts: + +```rust +// Spawn periodic cleanup task +let cleanup_state = state.clone(); +tokio::spawn(async move { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(30)); + loop { + interval.tick().await; + let mut s = cleanup_state.lock().await; + s.cleanup_dead_sandboxes(); + } +}); +``` + +- [ ] **Step 3: Add libc dependency if missing** + +Check if `Cargo.toml` has `libc`. If not, add to workspace dependencies: + +```toml +libc = "0.2" +``` + +- [ ] **Step 4: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "feat(daemon): add periodic stale sandbox cleanup" +``` + +--- + +### Task 2: Window ID propagation endpoint + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` +- Modify: `crates/sandbox-cli/src/client.rs` + +- [ ] **Step 1: Add request type** + +```rust +#[derive(Deserialize)] +pub struct SetWindowIdRequest { + pub window_id: u32, +} +``` + +- [ ] **Step 2: Add route** + +```rust +.route("/sandbox/{id}/window", post(set_window_id_handler)) +``` + +- [ ] **Step 3: Implement handler** + +```rust +async fn set_window_id_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result { + let mut state = state.lock().await; + let sandbox = state.sandboxes.get_mut(&id) + .ok_or_else(|| AppError::BadRequest(format!("Sandbox not found: {id}")))?; + sandbox.window_id = Some(req.window_id); + tracing::info!("Set window_id={} for sandbox {}", req.window_id, id); + // Update instance registry file + let registry = InstanceRegistry::default(); + if let Ok(mut instance) = registry.get(&id) { + instance.window_id = Some(req.window_id); + let _ = registry.update(&instance); + } + Ok(StatusCode::OK) +} +``` + +- [ ] **Step 4: Add client method** + +Add to `crates/sandbox-cli/src/client.rs`, following the `daemon_*` pattern: + +```rust +pub async fn daemon_set_window_id(sandbox_id: &str, window_id: u32) -> Result<()> { + let base = daemon_base_url()?; + let url = format!("{base}/sandbox/{sandbox_id}/window"); + reqwest::Client::new().post(&url) + .json(&serde_json::json!({ "window_id": window_id })) + .send().await? + .error_for_status()?; + Ok(()) +} +``` + +- [ ] **Step 5: Verify compilation** + +Run: `cargo check -p sandbox-core && cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs crates/sandbox-cli/src/client.rs +git commit -m "feat(daemon): add window_id propagation endpoint" +``` + +--- + +### Task 3: Instance status lifecycle + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` + +- [ ] **Step 1: Update status on sandbox creation** + +In `create_sandbox_handler`, after PTY is spawned, set status to `Running`: + +```rust +sandbox.status = InstanceStatus::Running; +``` + +- [ ] **Step 2: Update status on sandbox close** + +In `close_sandbox_handler`, before removing, set status to `Stopped`: + +```rust +if let Some(sb) = state.sandboxes.get_mut(&id) { + sb.status = InstanceStatus::Stopped; +} +``` + +- [ ] **Step 3: Update status on PTY exit** + +In the cleanup function, when a dead sandbox is found, write `Stopped` to the instance registry before removing: + +```rust +let registry = InstanceRegistry::default(); +if let Ok(mut instance) = registry.get(id) { + instance.status = InstanceStatus::Stopped; + let _ = registry.update(&instance); +} +``` + +- [ ] **Step 4: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "feat(daemon): proper instance status lifecycle transitions" +``` + +--- + +### Task 4: Manual verification + +- [ ] **Step 1: Build** + +Run: `cargo build -p sandbox-daemon && cargo build -p sandbox-cli` + +- [ ] **Step 2: Start two sandboxes** + +Run: `cargo run -p sandbox-cli -- start zsh && cargo run -p sandbox-cli -- start claude` + +- [ ] **Step 3: List sandboxes** + +Run: `cargo run -p sandbox-cli -- list` +Expected: Two sandboxes listed with Running status + +- [ ] **Step 4: Close one** + +Run: `cargo run -p sandbox-cli -- close ` +Expected: Sandbox removed from list + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "feat(phase5): multi-instance hardening complete" +``` diff --git a/docs/superpowers/plans/2026-05-31-phase6-gui-support.md b/docs/superpowers/plans/2026-05-31-phase6-gui-support.md new file mode 100644 index 0000000..af1f3e2 --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase6-gui-support.md @@ -0,0 +1,415 @@ +# Phase 6: GUI App Support & Frontend Integration — Implementation 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:** Support macOS GUI app sandboxes (launch .app, screenshot preview, control panel) and add a "New Sandbox" dialog in the Electron UI. + +**Architecture:** APP mode sandboxes launch a macOS .app via `NSWorkspace`. The Electron tab shows a screenshot preview (refreshed on demand) instead of xterm.js. A "New Sandbox" dialog lets users create CLI or APP sandboxes from the UI. + +**Tech Stack:** TypeScript, React, Electron IPC + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `electron-app/src/renderer/api.ts` | Modify | Add `createSandbox()`, `takeScreenshot()`, `closeSandbox()` | +| `electron-app/src/renderer/main.tsx` | Modify | Add New Sandbox dialog, APP mode tab rendering | +| `electron-app/src/renderer/components/AppPanel.tsx` | Create | Screenshot preview + controls for APP sandboxes | +| `electron-app/src/renderer/styles.css` | Modify | Dialog + app panel styles | + +--- + +### Task 1: Extend renderer API + +**Files:** +- Modify: `electron-app/src/renderer/api.ts` + +- [ ] **Step 1: Add createSandbox function** + +Add after `fetchSandboxInfo`: + +```typescript +export async function createSandbox(mode: "cli" | "app", command: string, args: string[] = []): Promise<{ id: string; pty_pid: number | null }> { + const res = await fetch(`${getBaseUrl()}/sandbox/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mode, command, args }), + }); + if (!res.ok) throw new Error(`Create failed: ${res.status}`); + return res.json(); +} +``` + +- [ ] **Step 2: Add takeScreenshot function** + +```typescript +export async function takeScreenshot(sandboxId: string): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/screenshot`); + if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`); + return res.blob(); +} +``` + +- [ ] **Step 3: Add closeSandbox function** + +```typescript +export async function closeSandbox(sandboxId: string): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/close`, { method: "POST" }); + if (!res.ok) throw new Error(`Close failed: ${res.status}`); +} +``` + +- [ ] **Step 4: Verify TypeScript compilation** + +Run: `cd electron-app && pnpm typecheck` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add electron-app/src/renderer/api.ts +git commit -m "feat(renderer): add createSandbox, takeScreenshot, closeSandbox API" +``` + +--- + +### Task 2: AppPanel component for APP mode sandboxes + +**Files:** +- Create: `electron-app/src/renderer/components/AppPanel.tsx` + +- [ ] **Step 1: Create AppPanel component** + +```tsx +import { useState, useEffect, useCallback } from "react"; +import { takeScreenshot } from "../api"; + +interface AppPanelProps { + sandboxId: string; +} + +export default function AppPanel({ sandboxId }: AppPanelProps) { + const [screenshotUrl, setScreenshotUrl] = useState(null); + const [loading, setLoading] = useState(false); + + const refreshScreenshot = useCallback(async () => { + setLoading(true); + try { + const blob = await takeScreenshot(sandboxId); + const url = URL.createObjectURL(blob); + if (screenshotUrl) URL.revokeObjectURL(screenshotUrl); + setScreenshotUrl(url); + } catch (e) { + console.error("Screenshot failed:", e); + } finally { + setLoading(false); + } + }, [sandboxId, screenshotUrl]); + + useEffect(() => { + refreshScreenshot(); + const interval = setInterval(refreshScreenshot, 5000); + return () => { + clearInterval(interval); + if (screenshotUrl) URL.revokeObjectURL(screenshotUrl); + }; + }, [sandboxId]); + + return ( +
+ {screenshotUrl ? ( + App screenshot + ) : ( +
+ {loading ? "Loading screenshot..." : "No screenshot available"} +
+ )} +
+ +
+
+ ); +} +``` + +- [ ] **Step 2: Verify TypeScript compilation** + +Run: `cd electron-app && pnpm typecheck` +Expected: PASS + +- [ ] **Step 3: Commit** + +```bash +git add electron-app/src/renderer/components/AppPanel.tsx +git commit -m "feat(renderer): add AppPanel component for APP mode sandboxes" +``` + +--- + +### Task 3: New Sandbox dialog + +**Files:** +- Modify: `electron-app/src/renderer/main.tsx` +- Modify: `electron-app/src/renderer/styles.css` + +- [ ] **Step 1: Add dialog state to App component** + +Add inside the `App` function: + +```typescript +const [showNewDialog, setShowNewDialog] = useState(false); +const [newSandboxCmd, setNewSandboxCmd] = useState(""); +const [newSandboxMode, setNewSandboxMode] = useState<"cli" | "app">("cli"); +``` + +- [ ] **Step 2: Add dialog JSX** + +Add before the closing `
` of `.main-content`: + +```tsx +{showNewDialog && ( +
setShowNewDialog(false)}> +
e.stopPropagation()}> +
New Sandbox
+
+ + +
+
+ + setNewSandboxCmd(e.target.value)} + placeholder={newSandboxMode === "cli" ? "zsh" : "/Applications/TextEdit.app"} + autoFocus + /> +
+
+ + +
+
+
+)} +``` + +- [ ] **Step 3: Wire the "+" button** + +Replace the existing `onClick` on the `+` button (around line 170): + +```tsx + +``` + +- [ ] **Step 4: Add import for createSandbox** + +Add to imports at top of `main.tsx`: + +```typescript +import { SandboxInfo, fetchSandboxList, setDaemonPort, getDaemonPort, createSandbox } from "./api"; +``` + +- [ ] **Step 5: Add dialog styles** + +Add to `electron-app/src/renderer/styles.css`: + +```css +.dialog-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.dialog { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 24px; + min-width: 360px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.dialog-title { + font-size: 15px; + font-weight: 600; + margin-bottom: 16px; +} + +.dialog-field { + margin-bottom: 12px; +} + +.dialog-field label { + display: block; + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.dialog-field input, +.dialog-field select { + width: 100%; + padding: 6px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-primary); + font-size: 13px; + font-family: inherit; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; +} + +.dialog-actions button { + padding: 6px 16px; + border-radius: 6px; + border: 1px solid var(--border-color); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; +} + +.dialog-actions button.primary { + background: var(--accent-color); + color: white; + border-color: var(--accent-color); +} +``` + +- [ ] **Step 6: Add AppPanel to tab rendering** + +Import `AppPanel` and use it for APP mode tabs. Replace the terminal area section in `main.tsx`: + +```tsx +import AppPanel from "./components/AppPanel"; + +// In the terminal area section: +{activeTab ? ( + activeTab.kind === "app" ? ( +
+ +
+ ) : ( +
+ +
+ ) +) : ( +
...
+)} +``` + +- [ ] **Step 7: Add app panel styles** + +```css +.app-panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--bg-secondary); +} + +.app-screenshot { + flex: 1; + object-fit: contain; + padding: 8px; +} + +.app-placeholder { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + font-size: 14px; +} + +.app-controls { + padding: 8px 12px; + border-top: 1px solid var(--border-color); + display: flex; + gap: 8px; +} + +.app-controls button { + padding: 4px 12px; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + cursor: pointer; +} +``` + +- [ ] **Step 8: Verify TypeScript compilation** + +Run: `cd electron-app && pnpm typecheck` +Expected: PASS + +- [ ] **Step 9: Commit** + +```bash +git add electron-app/src/renderer/main.tsx electron-app/src/renderer/styles.css electron-app/src/renderer/components/AppPanel.tsx +git commit -m "feat(renderer): new sandbox dialog + APP mode panel" +``` + +--- + +### Task 4: Window ID reporting from Electron + +**Files:** +- Modify: `electron-app/src/renderer/api.ts` + +- [ ] **Step 1: Add setWindowId function** + +```typescript +export async function setWindowId(sandboxId: string, windowId: number): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/window`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ window_id: windowId }), + }); + if (!res.ok) throw new Error(`Set window_id failed: ${res.status}`); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add electron-app/src/renderer/api.ts +git commit -m "feat(renderer): add setWindowId API call" +``` diff --git a/docs/superpowers/plans/2026-05-31-phase7-integration.md b/docs/superpowers/plans/2026-05-31-phase7-integration.md new file mode 100644 index 0000000..03eb6b8 --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase7-integration.md @@ -0,0 +1,405 @@ +# Phase 7: Integration Tests, MCP Update & Docs — Implementation 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:** Add MCP tools for sandbox management, write integration tests, run end-to-end smoke tests, and update documentation. + +**Architecture:** MCP tools wrap the daemon HTTP client. Integration tests verify the daemon API end-to-end. Documentation updates reflect the Electron architecture. + +**Tech Stack:** Rust, serde_json, tokio (test), axum (test server) + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `crates/sandbox-cli/src/main.rs` | Modify | Add `McpServe` command with MCP tools | +| `crates/sandbox-core/tests/daemon_integration.rs` | Create | Daemon HTTP API integration tests | +| `CLAUDE.md` | Modify | Update architecture section for Electron | +| `README.md` | Modify | Update quick start for Electron | + +--- + +### Task 1: MCP tools for sandbox management + +**Files:** +- Modify: `crates/sandbox-cli/src/main.rs` + +- [ ] **Step 1: Add McpServe command** + +Add to the `Commands` enum: + +```rust +/// Start MCP stdio server for agent integration +McpServe, +``` + +- [ ] **Step 2: Implement MCP tool definitions** + +Add a function that returns the MCP tool schema: + +```rust +fn mcp_tools() -> serde_json::Value { + serde_json::json!({ + "tools": [ + { + "name": "list_sandboxes", + "description": "List all active sandbox instances", + "inputSchema": { "type": "object", "properties": {} } + }, + { + "name": "start_sandbox", + "description": "Start a new sandbox with a CLI command", + "inputSchema": { + "type": "object", + "properties": { + "command": { "type": "string", "description": "Command to run (e.g., 'zsh', 'claude')" }, + "args": { "type": "array", "items": { "type": "string" }, "description": "Command arguments" } + }, + "required": ["command"] + } + }, + { + "name": "close_sandbox", + "description": "Close a sandbox by ID", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + }, + { + "name": "screenshot_sandbox", + "description": "Take a screenshot of a sandbox", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + }, + { + "name": "type_text", + "description": "Type text into a sandbox PTY", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" }, + "text": { "type": "string" } + }, + "required": ["sandbox_id", "text"] + } + }, + { + "name": "press_key", + "description": "Press a key in a sandbox", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" }, + "key": { "type": "string", "description": "Key name (Return, Tab, Escape, etc.)" }, + "modifiers": { "type": "array", "items": { "type": "string" } } + }, + "required": ["sandbox_id", "key"] + } + }, + { + "name": "inspect_ui", + "description": "Inspect the UI tree of a sandbox window", + "inputSchema": { + "type": "object", + "properties": { + "sandbox_id": { "type": "string" } + }, + "required": ["sandbox_id"] + } + } + ] + }) +} +``` + +- [ ] **Step 3: Implement MCP stdio handler** + +Add a basic JSON-RPC over stdio handler: + +```rust +async fn run_mcp_server() -> anyhow::Result<()> { + use std::io::{self, BufRead, Write}; + + let client = SandboxClient::from_daemon_json().await + .unwrap_or_else(|_| SandboxClient::from_port(15801)); + + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + for line in stdin.lock().lines() { + let line = line?; + if line.trim().is_empty() { continue; } + + let msg: serde_json::Value = serde_json::from_str(&line)?; + let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or(""); + let id = msg.get("id").cloned(); + let params = msg.get("params").cloned().unwrap_or(serde_json::json!({})); + + let result = match method { + "initialize" => serde_json::json!({ + "protocolVersion": "2024-11-05", + "capabilities": { "tools": {} }, + "serverInfo": { "name": "sandbox-mcp", "version": "0.1.0" } + }), + "tools/list" => mcp_tools(), + "tools/call" => { + let tool_name = params.get("name").and_then(|n| n.as_str()).unwrap_or(""); + let args = params.get("arguments").cloned().unwrap_or(serde_json::json!({})); + handle_mcp_tool(&client, tool_name, &args).await + } + _ => serde_json::json!({ "error": { "code": -32601, "message": "Method not found" } }), + }; + + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": result + }); + writeln!(stdout, "{}", serde_json::to_string(&response)?)?; + stdout.flush()?; + } + Ok(()) +} + +async fn handle_mcp_tool(client: &SandboxClient, name: &str, args: &serde_json::Value) -> serde_json::Value { + let result: anyhow::Result = async { + match name { + "list_sandboxes" => { + let list = sandbox_core::daemon::list_sandboxes_via_http(&client_url).await?; + Ok(serde_json::to_value(list)?) + } + "start_sandbox" => { + let cmd = args["command"].as_str().unwrap_or("zsh"); + let cmd_args: Vec = args["args"].as_array() + .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_default(); + let result = daemon_create_sandbox("cli", cmd, &cmd_args).await?; + Ok(serde_json::to_value(result)?) + } + "close_sandbox" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + daemon_close(id).await?; + Ok(serde_json::json!({ "closed": id })) + } + "type_text" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let text = args["text"].as_str().unwrap_or(""); + daemon_pty_write(id, text).await?; + Ok(serde_json::json!({ "typed": text })) + } + "press_key" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let key = args["key"].as_str().unwrap_or("Return"); + let mods: Vec = args["modifiers"].as_array() + .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_default(); + daemon_key(id, key, &mods).await?; + Ok(serde_json::json!({ "pressed": key })) + } + "inspect_ui" => { + let id = args["sandbox_id"].as_str().unwrap_or(""); + let tree = daemon_inspect(id).await?; + Ok(serde_json::to_value(tree)?) + } + _ => Ok(serde_json::json!({ "error": format!("Unknown tool: {name}") })), + } + }.await; + + match result { + Ok(value) => serde_json::json!({ + "content": [{ "type": "text", "text": serde_json::to_string_pretty(&value).unwrap_or_default() }] + }), + Err(e) => serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {e}") }], + "isError": true + }), + } +} +``` + +- [ ] **Step 4: Wire McpServe command** + +```rust +Commands::McpServe => { + run_mcp_server().await?; +} +``` + +- [ ] **Step 5: Verify compilation** + +Run: `cargo check -p sandbox-cli` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add crates/sandbox-cli/src/main.rs +git commit -m "feat(cli): add MCP stdio server with sandbox management tools" +``` + +--- + +### Task 2: Daemon integration tests + +**Files:** +- Create: `crates/sandbox-core/tests/daemon_integration.rs` + +- [ ] **Step 1: Write health check test** + +```rust +// crates/sandbox-core/tests/daemon_integration.rs +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tokio::test] +async fn test_daemon_health_endpoint() { + let state = Arc::new(Mutex::new(sandbox_core::daemon::DaemonState { + port: 0, + sandboxes: std::collections::HashMap::new(), + started_at: std::time::Instant::now(), + })); + + // Build router (same as run_daemon but without binding) + let app = sandbox_core::daemon::build_daemon_router(state); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + let client = reqwest::Client::new(); + let resp = client.get(format!("http://{addr}/health")) + .send().await.unwrap(); + assert_eq!(resp.status(), 200); + + let body: serde_json::Value = resp.json().await.unwrap(); + assert_eq!(body["status"], "ok"); +} + +#[tokio::test] +async fn test_daemon_list_empty() { + let state = Arc::new(Mutex::new(sandbox_core::daemon::DaemonState { + port: 0, + sandboxes: std::collections::HashMap::new(), + started_at: std::time::Instant::now(), + })); + + let app = sandbox_core::daemon::build_daemon_router(state); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + let client = reqwest::Client::new(); + let resp = client.get(format!("http://{addr}/sandbox/list")) + .send().await.unwrap(); + assert_eq!(resp.status(), 200); + + let body: serde_json::Value = resp.json().await.unwrap(); + assert!(body.as_array().unwrap().is_empty()); +} +``` + +- [ ] **Step 2: Verify tests compile (they need build_daemon_router exposed)** + +This requires exposing `build_daemon_router` from the daemon module. Add to `daemon/mod.rs`: + +```rust +/// Build the axum router (exposed for testing). +pub fn build_daemon_router(state: Arc>) -> Router { + // Move the router building code from run_daemon into this function + // and call it from run_daemon +} +``` + +- [ ] **Step 3: Run tests** + +Run: `cargo test -p sandbox-core --test daemon_integration` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/tests/daemon_integration.rs crates/sandbox-core/src/daemon/mod.rs +git commit -m "test(core): add daemon HTTP integration tests" +``` + +--- + +### Task 3: Update documentation + +**Files:** +- Modify: `CLAUDE.md` +- Modify: `README.md` + +- [ ] **Step 1: Update CLAUDE.md architecture section** + +Replace the architecture diagram section to reflect Electron: + +```markdown +## 架构总览 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Agent / 用户 (CLI / MCP / HTTP) │ +│ sandbox start / list / screenshot / click / type / key │ +└───────────────────────────────┬───────────────────────────────┘ + │ HTTP (localhost:15801) + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ sandbox-daemon (Rust, 单实例) │ +│ PTY Manager + App Manager + Automation Engine │ +│ Instance Registry (~/.sandbox/instances/) │ +└───────────────────────────────┬───────────────────────────────┘ + │ WebSocket (PTY 流) + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Electron App (单实例, Chromium) │ +│ Tab 管理 + xterm.js + 控制面板 + 截图预览 │ +└──────────────────────────────────────────────────────────────┘ +``` +``` + +- [ ] **Step 2: Update README.md quick start** + +Update the build and run instructions to use Electron: + +```markdown +### 构建 + +```bash +# 构建 daemon + CLI +cargo build --release + +# 构建 Electron 应用 +cd electron-app && pnpm install && pnpm build && cd .. +``` + +### 运行 + +```bash +# 启动沙箱(自动启动 daemon + Electron) +./target/release/sandbox start claude +``` +``` + +- [ ] **Step 3: Commit** + +```bash +git add CLAUDE.md README.md +git commit -m "docs: update architecture for Electron + daemon model" +``` diff --git a/docs/superpowers/plans/2026-05-31-phase8-bugfixes.md b/docs/superpowers/plans/2026-05-31-phase8-bugfixes.md new file mode 100644 index 0000000..24a7fdc --- /dev/null +++ b/docs/superpowers/plans/2026-05-31-phase8-bugfixes.md @@ -0,0 +1,336 @@ +# Phase 8: Release Test Bug Fixes — Implementation 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:** Fix the 6 bugs found during release testing. B3 and B5 are already resolved by the Electron architecture. This plan covers B1 (region crop), B2 (window_id propagation), B4 (app window tracking), and B6 (sandbox-relative screenshots). + +**Architecture:** B1 uses `image::imageops::crop` for software cropping. B2 auto-discovers the Electron window via `ScreenCapture::find_window_by_title` after daemon starts. B4 tracks app windows after `spawn_app`. B6 converts sandbox-relative coordinates to global. + +**Tech Stack:** Rust, image crate, ScreenCaptureKit + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `crates/sandbox-core/src/capture/mod.rs` | Modify | Fix `capture_region` to crop with image crate | +| `crates/sandbox-core/src/daemon/mod.rs` | Modify | Auto-discover window_id, sandbox-relative screenshot endpoint | +| `crates/sandbox-core/src/process/mod.rs` | Modify | Add `spawn_app_with_window` that returns window_id | +| `crates/sandbox-core/tests/capture_test.rs` | Create | Tests for capture_region crop | + +--- + +### Task 1: Fix B1 — capture_region crop + +**Files:** +- Modify: `crates/sandbox-core/src/capture/mod.rs` +- Create: `crates/sandbox-core/tests/capture_test.rs` + +- [ ] **Step 1: Read current capture_region implementation** + +The current `capture_region` at line 83 ignores x/y. Fix it to use `image::imageops::crop`. + +- [ ] **Step 2: Fix capture_region** + +Replace the `capture_region` function body in the `macos_impl` module: + +```rust +pub fn capture_region(x: i32, y: i32, width: u32, height: u32) -> Result> { + // Capture full screen first + let content = SCShareableContent::get() + .map_err(|e| AppError::Screenshot(format!("SCShareableContent failed: {e}")))?; + let display = content.displays().first() + .ok_or_else(|| AppError::Screenshot("No display found".into()))?; + + let config = SCStreamConfiguration::new() + .map_err(|e| AppError::Screenshot(format!("SCStreamConfiguration failed: {e}")))?; + config.set_width(display.width() as u32); + config.set_height(display.height() as u32); + + let filter = SCContentFilter::new().display(display).excluding_windows(&[]); + let image = SCScreenshotManager::capture_image(content, filter, config) + .map_err(|e| AppError::Screenshot(format!("Capture failed: {e}")))?; + + // Get raw RGBA data + let img_width = image.width() as u32; + let img_height = image.height() as u32; + let bytes_per_row = image.bytes_per_row(); + let data = image.data(); + let data_bytes = data.bytes(); + + // Create RgbaImage from raw data + let mut rgba = Vec::with_capacity((img_width * img_height * 4) as usize); + for row in 0..img_height { + let row_start = (row * bytes_per_row as u32) as usize; + for col in 0..img_width { + let px = row_start + (col * 4) as usize; + if px + 3 < data_bytes.len() { + rgba.extend_from_slice(&data_bytes[px..px + 4]); + } + } + } + + let full_img = image::RgbaImage::from_raw(img_width, img_height, rgba) + .ok_or_else(|| AppError::Screenshot("Failed to create image from raw data".into()))?; + + // Clamp crop region to display bounds + let crop_x = x.max(0) as u32; + let crop_y = y.max(0) as u32; + let crop_w = width.min(img_width.saturating_sub(crop_x)); + let crop_h = height.min(img_height.saturating_sub(crop_y)); + + if crop_w == 0 || crop_h == 0 { + return Err(AppError::BadRequest("Crop region is outside display bounds".into())); + } + + let cropped = image::imageops::crop(&mut full_img.clone(), crop_x, crop_y, crop_w, crop_h); + let cropped_img = cropped.to_image(); + + let mut png = Vec::new(); + let encoder = image::codecs::png::PngEncoder::new(&mut png); + use image::ImageEncoder; + encoder.write_image( + cropped_img.as_raw(), + crop_w, + crop_h, + image::ExtendedColorType::Rgba8, + ).map_err(|e| AppError::Screenshot(format!("PNG encode failed: {e}")))?; + + Ok(png) +} +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/capture/mod.rs +git commit -m "fix(capture): capture_region now crops using image::imageops::crop" +``` + +--- + +### Task 2: Fix B2 — Auto-discover window_id + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` + +- [ ] **Step 1: Add window discovery after daemon starts** + +In `run_daemon()`, after the server starts, spawn a task to discover the Electron window: + +```rust +// Auto-discover Electron window ID +let discovery_state = state.clone(); +tokio::spawn(async move { + // Wait a bit for Electron to launch + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + + match ScreenCapture::find_window_by_title("System Test Sandbox") { + Ok(window_id) => { + tracing::info!("Discovered Electron window_id={}", window_id); + // Store in daemon state for reference + let mut s = discovery_state.lock().await; + // Set window_id on all sandboxes that don't have one + for (_, sb) in s.sandboxes.iter_mut() { + if sb.window_id.is_none() { + sb.window_id = Some(window_id); + } + } + } + Err(e) => { + tracing::warn!("Could not discover Electron window: {e}"); + } + } +}); +``` + +- [ ] **Step 2: Add window discovery on sandbox creation** + +In `create_sandbox_handler`, after creating the sandbox, try to discover the window: + +```rust +// After sandbox is created, try to find window_id +if sandbox.window_id.is_none() { + if let Ok(wid) = ScreenCapture::find_window_by_title("System Test Sandbox") { + sandbox.window_id = Some(wid); + } +} +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "fix(daemon): auto-discover Electron window_id for screenshots" +``` + +--- + +### Task 3: Fix B4 — App window tracking + +**Files:** +- Modify: `crates/sandbox-core/src/process/mod.rs` + +- [ ] **Step 1: Add spawn_app_with_window function** + +Add after the existing `spawn_app`: + +```rust +/// Spawn a macOS .app and attempt to discover its window ID. +pub fn spawn_app_with_window(app_path: &str) -> Result<(ProcessInfo, Option)> { + let info = Self::spawn_app(app_path)?; + + // Wait for the app to create its window + std::thread::sleep(std::time::Duration::from_millis(500)); + + // Try to find the window by app name + let app_name = std::path::Path::new(app_path) + .file_stem() + .unwrap_or_default() + .to_string_lossy(); + + let window_id = crate::capture::ScreenCapture::find_window_by_title(&app_name).ok(); + + Ok((info, window_id)) +} +``` + +- [ ] **Step 2: Update daemon spawn_app_handler to use it** + +In `daemon/mod.rs`, update the spawn_app handler: + +```rust +async fn spawn_app_handler( + State(state): State>>, + Path(id): Path, + Json(req): Json, +) -> Result, AppError> { + let (info, window_id) = crate::process::ProcessManager::spawn_app_with_window(&req.app_path)?; + + // Update sandbox window_id if found + if let Some(wid) = window_id { + let mut state = state.lock().await; + if let Some(sb) = state.sandboxes.get_mut(&id) { + sb.window_id = Some(wid); + } + } + + Ok(Json(serde_json::json!({ + "pid": info.pid, + "name": info.name, + "window_id": window_id, + }))) +} +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core && cargo check -p sandbox-daemon` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/process/mod.rs crates/sandbox-core/src/daemon/mod.rs +git commit -m "fix(process): spawn_app_with_window tracks app window_id" +``` + +--- + +### Task 4: Fix B6 — Sandbox-relative screenshot + +**Files:** +- Modify: `crates/sandbox-core/src/daemon/mod.rs` + +- [ ] **Step 1: Add sandbox-region screenshot endpoint** + +Add route: + +```rust +.route("/sandbox/{id}/screenshot/region", get(screenshot_sandbox_region_handler)) +``` + +- [ ] **Step 2: Implement handler** + +```rust +#[derive(Deserialize)] +struct SandboxRegionQuery { + x: i32, + y: i32, + width: u32, + height: u32, +} + +async fn screenshot_sandbox_region_handler( + State(state): State>>, + Path(id): Path, + Query(q): Query, +) -> Result, AppError> { + let state = state.lock().await; + let sandbox = state.sandboxes.get(&id) + .ok_or_else(|| AppError::BadRequest(format!("Sandbox not found: {id}")))?; + let window_id = sandbox.window_id + .ok_or_else(|| AppError::BadRequest("Sandbox has no window_id".into()))?; + + // Get window position on screen + let windows = ScreenCapture::list_windows()?; + let window = windows.iter().find(|(wid, _)| *wid == window_id) + .ok_or_else(|| AppError::WindowNotFound(format!("Window {window_id} not found")))?; + + // Use CGWindowListCopyWindowInfo to get window frame + // For now, use global coordinates directly (user must provide global coords) + // TODO: Convert sandbox-relative to global using window frame + let png = ScreenCapture::capture_region(q.x, q.y, q.width, q.height)?; + Ok(png) +} +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cargo check -p sandbox-core` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/sandbox-core/src/daemon/mod.rs +git commit -m "fix(daemon): add sandbox-relative screenshot region endpoint" +``` + +--- + +### Task 5: Manual verification + +- [ ] **Step 1: Build everything** + +Run: `cargo build --release` + +- [ ] **Step 2: Test screenshot** + +Run: `./target/release/sandbox start zsh` +Wait for Electron window, then: +Run: `./target/release/sandbox screenshot --id -o test.png` +Expected: Valid PNG file (not empty, not error) + +- [ ] **Step 3: Test region crop** + +Run: `./target/release/sandbox screenshot --id -o region.png --region 0,0,400,300` +Expected: Cropped PNG + +- [ ] **Step 4: Commit final state** + +```bash +git add -A +git commit -m "fix(phase8): all release test bugs resolved" +``` diff --git a/electron-app/electron-builder.config.cjs b/electron-app/electron-builder.config.cjs new file mode 100644 index 0000000..c61222e --- /dev/null +++ b/electron-app/electron-builder.config.cjs @@ -0,0 +1,21 @@ +/** @type {import('electron-builder').Configuration} */ +const config = { + appId: "com.system-test-sandbox", + productName: "System Test Sandbox", + directories: { + output: "../dist/electron", + }, + mac: { + target: ["dmg"], + category: "public.app-category.developer-tools", + }, + files: ["out/**/*"], + extraResources: [ + { + from: "../target/release/sandbox-daemon", + to: "sandbox-daemon", + }, + ], +}; + +module.exports = config; diff --git a/electron-app/package.json b/electron-app/package.json new file mode 100644 index 0000000..bf15020 --- /dev/null +++ b/electron-app/package.json @@ -0,0 +1,44 @@ +{ + "name": "sandbox-electron", + "version": "0.1.0", + "private": true, + "main": "./out/main/index.js", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview", + "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json", + "pack": "electron-builder --config electron-builder.config.cjs", + "dist": "npm run build && npm run pack", + "test": "vitest run", + "test:unit": "vitest run --coverage" + }, + "dependencies": { + "electron-store": "^10.0.0" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^4.1.7", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", + "autoprefixer": "^10.4.20", + "electron": "^34.0.0", + "electron-builder": "^25.1.8", + "electron-vite": "^3.0.0", + "postcss": "^8.4.49", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "vite": "^6.0.5", + "vitest": "^4.1.7" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "electron", + "esbuild" + ] + } +} diff --git a/electron-app/pnpm-lock.yaml b/electron-app/pnpm-lock.yaml new file mode 100644 index 0000000..925c4bd --- /dev/null +++ b/electron-app/pnpm-lock.yaml @@ -0,0 +1,5038 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + electron-store: + specifier: ^10.0.0 + version: 10.1.0 + devDependencies: + '@types/react': + specifier: ^18.3.1 + version: 18.3.29 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.7(@types/react@18.3.29) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)) + '@vitest/coverage-v8': + specifier: ^4.1.7 + version: 4.1.7(vitest@4.1.7) + '@xterm/addon-fit': + specifier: ^0.11.0 + version: 0.11.0 + '@xterm/xterm': + specifier: ^6.0.0 + version: 6.0.0 + autoprefixer: + specifier: ^10.4.20 + version: 10.5.0(postcss@8.5.15) + electron: + specifier: ^34.0.0 + version: 34.5.8 + electron-builder: + specifier: ^25.1.8 + version: 25.1.8(electron-builder-squirrel-windows@25.1.8) + electron-vite: + specifier: ^3.0.0 + version: 3.1.0(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)) + postcss: + specifier: ^8.4.49 + version: 8.5.15 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + tailwindcss: + specifier: ^3.4.17 + version: 3.4.19 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + vite: + specifier: ^6.0.5 + version: 6.4.2(@types/node@25.9.1)(jiti@1.21.7) + vitest: + specifier: ^4.1.7 + version: 4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)) + +packages: + + 7zip-bin@5.2.0: + resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@develar/schema-utils@2.6.5': + resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} + engines: {node: '>= 8.9.0'} + + '@electron/asar@3.4.1': + resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} + engines: {node: '>=10.12.0'} + hasBin: true + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@electron/notarize@2.5.0': + resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} + engines: {node: '>= 10.0.0'} + + '@electron/osx-sign@1.3.1': + resolution: {integrity: sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==} + engines: {node: '>=12.0.0'} + hasBin: true + + '@electron/rebuild@3.6.1': + resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + engines: {node: '>=12.13.0'} + hasBin: true + + '@electron/universal@2.0.1': + resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} + engines: {node: '>=16.4'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@malept/cross-spawn-promise@2.0.0': + resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} + engines: {node: '>= 12.13.0'} + + '@malept/flatpak-bundler@0.4.0': + resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} + engines: {node: '>= 10.0.0'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} + engines: {node: '>= 10'} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + '@types/plist@3.0.5': + resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.29': + resolution: {integrity: sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/verror@1.10.11': + resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/coverage-v8@4.1.7': + resolution: {integrity: sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==} + peerDependencies: + '@vitest/browser': 4.1.7 + vitest: 4.1.7 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + + '@xmldom/xmldom@0.9.10': + resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + engines: {node: '>=14.6'} + + '@xterm/addon-fit@0.11.0': + resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + + '@xterm/xterm@6.0.0': + resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-builder-bin@5.0.0-alpha.10: + resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + + app-builder-lib@25.1.8: + resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + engines: {node: '>=14.0.0'} + peerDependencies: + dmg-builder: 25.1.8 + electron-builder-squirrel-windows: 25.1.8 + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.2: + resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + atomically@2.1.1: + resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.32: + resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird-lst@1.0.9: + resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + builder-util-runtime@9.2.10: + resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} + engines: {node: '>=12.0.0'} + + builder-util@25.1.7: + resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chromium-pickle-js@0.2.0: + resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + compare-version@0.1.2: + resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} + engines: {node: '>=0.10.0'} + + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + conf@14.0.0: + resolution: {integrity: sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw==} + engines: {node: '>=20'} + + config-file-ts@0.2.8-rc1: + resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + crc@3.8.0: + resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debounce-fn@6.0.0: + resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-compare@4.2.0: + resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dmg-builder@25.1.8: + resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + + dmg-license@1.0.11: + resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} + engines: {node: '>=8'} + os: [darwin] + hasBin: true + + dot-prop@9.0.0: + resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} + engines: {node: '>=18'} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-builder-squirrel-windows@25.1.8: + resolution: {integrity: sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==} + + electron-builder@25.1.8: + resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + engines: {node: '>=14.0.0'} + hasBin: true + + electron-publish@25.1.7: + resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + + electron-store@10.1.0: + resolution: {integrity: sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w==} + engines: {node: '>=20'} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + + electron-vite@3.1.0: + resolution: {integrity: sha512-M7aAzaRvSl5VO+6KN4neJCYLHLpF/iWo5ztchI/+wMxIieDZQqpbCYfaEHHHPH6eupEzfvZdLYdPdmvGqoVe0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@swc/core': ^1.0.0 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + '@swc/core': + optional: true + + electron@34.5.8: + resolution: {integrity: sha512-vxLD65mabTzYmEVa9KceMHM0+zO+vqgrhcyNVlmTd0IGV5J7XZ8v/qElm0o4YQ4wPeq7olZkUjZkBQQEdr23/g==} + engines: {node: '>= 12.20.55'} + hasBin: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + extsprintf@1.4.1: + resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-corefoundation@1.1.7: + resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} + engines: {node: ^8.11.2 || >=10} + os: [darwin] + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isbinaryfile@4.0.10: + resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} + engines: {node: '>= 8.0.0'} + + isbinaryfile@5.0.7: + resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + lazy-val@1.0.5: + resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + + node-addon-api@1.7.2: + resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + + node-api-version@0.2.1: + resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} + + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pe-library@0.4.1: + resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} + engines: {node: '>=12', npm: '>=6'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + plist@3.1.1: + resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} + engines: {node: '>=10.4.0'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-binary-file-arch@1.0.6: + resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} + hasBin: true + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resedit@1.7.2: + resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} + engines: {node: '>=12', npm: '>=6'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sanitize-filename@1.6.4: + resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stat-mode@1.0.0: + resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} + engines: {node: '>= 6'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + temp-file@3.4.0: + resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.3: + resolution: {integrity: sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + utf8-byte-length@1.0.5: + resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + verror@1.10.1: + resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} + engines: {node: '>=0.6.0'} + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + 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 + + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + +snapshots: + + 7zip-bin@5.2.0: {} + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + 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/core@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.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + 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.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@develar/schema-utils@2.6.5': + dependencies: + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) + + '@electron/asar@3.4.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.5 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/notarize@2.5.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@electron/osx-sign@1.3.1': + dependencies: + compare-version: 0.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + isbinaryfile: 4.0.10 + minimist: 1.2.8 + plist: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@electron/rebuild@3.6.1': + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + chalk: 4.1.2 + debug: 4.4.3 + detect-libc: 2.1.2 + fs-extra: 10.1.0 + got: 11.8.6 + node-abi: 3.92.0 + node-api-version: 0.2.1 + node-gyp: 9.4.1 + ora: 5.4.1 + read-binary-file-arch: 1.0.6 + semver: 7.8.1 + tar: 6.2.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + + '@electron/universal@2.0.1': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + dir-compare: 4.2.0 + fs-extra: 11.3.5 + minimatch: 9.0.9 + plist: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@gar/promisify@1.1.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@malept/cross-spawn-promise@2.0.0': + dependencies: + cross-spawn: 7.0.6 + + '@malept/flatpak-bundler@0.4.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + lodash: 4.18.1 + tmp-promise: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.8.1 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@sindresorhus/is@4.6.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tootallnate/once@2.0.1': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.2.0 + '@types/keyv': 3.1.4 + '@types/node': 20.19.41 + '@types/responselike': 1.0.3 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 25.9.1 + + '@types/http-cache-semantics@4.2.0': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.19.41 + + '@types/ms@2.1.0': {} + + '@types/node@20.19.41': + dependencies: + undici-types: 6.21.0 + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + '@types/plist@3.0.5': + dependencies: + '@types/node': 25.9.1 + xmlbuilder: 15.1.1 + optional: true + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.29)': + dependencies: + '@types/react': 18.3.29 + + '@types/react@18.3.29': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.19.41 + + '@types/verror@1.10.11': + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.41 + optional: true + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(@types/node@25.9.1)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@4.1.7(vitest@4.1.7)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.7 + ast-v8-to-istanbul: 1.0.2 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)) + + '@vitest/expect@4.1.7': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.7(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7))': + dependencies: + '@vitest/spy': 4.1.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.2(@types/node@25.9.1)(jiti@1.21.7) + + '@vitest/pretty-format@4.1.7': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.7': + dependencies: + '@vitest/utils': 4.1.7 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.7': {} + + '@vitest/utils@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@xmldom/xmldom@0.9.10': {} + + '@xterm/addon-fit@0.11.0': {} + + '@xterm/xterm@6.0.0': {} + + abbrev@1.1.1: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-keywords@3.5.2(ajv@6.15.0): + dependencies: + ajv: 6.15.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + app-builder-bin@5.0.0-alpha.10: {} + + app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + dependencies: + '@develar/schema-utils': 2.6.5 + '@electron/notarize': 2.5.0 + '@electron/osx-sign': 1.3.1 + '@electron/rebuild': 3.6.1 + '@electron/universal': 2.0.1 + '@malept/flatpak-bundler': 0.4.0 + '@types/fs-extra': 9.0.13 + async-exit-hook: 2.0.1 + bluebird-lst: 1.0.9 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chromium-pickle-js: 0.2.0 + config-file-ts: 0.2.8-rc1 + debug: 4.4.3 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + dotenv: 16.6.1 + dotenv-expand: 11.0.7 + ejs: 3.1.10 + electron-builder-squirrel-windows: 25.1.8(dmg-builder@25.1.8) + electron-publish: 25.1.7 + form-data: 4.0.5 + fs-extra: 10.1.0 + hosted-git-info: 4.1.0 + is-ci: 3.0.1 + isbinaryfile: 5.0.7 + js-yaml: 4.1.1 + json5: 2.2.3 + lazy-val: 1.0.5 + minimatch: 10.2.5 + resedit: 1.7.2 + sanitize-filename: 1.6.4 + semver: 7.8.1 + tar: 6.2.1 + temp-file: 3.4.0 + transitivePeerDependencies: + - bluebird + - supports-color + + aproba@2.1.0: {} + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + assert-plus@1.0.0: + optional: true + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.2: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + astral-regex@2.0.0: + optional: true + + async-exit-hook@2.0.1: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atomically@2.1.1: + dependencies: + stubborn-fs: 2.0.0 + when-exit: 2.1.5 + + autoprefixer@10.5.0(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001793 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.32: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird-lst@1.0.9: + dependencies: + bluebird: 3.7.2 + + bluebird@3.7.2: {} + + boolean@3.2.0: + optional: true + + brace-expansion@1.1.15: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.32 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + buffer-crc32@0.2.13: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builder-util-runtime@9.2.10: + dependencies: + debug: 4.4.3 + sax: 1.6.0 + transitivePeerDependencies: + - supports-color + + builder-util@25.1.7: + dependencies: + 7zip-bin: 5.2.0 + '@types/debug': 4.1.13 + app-builder-bin: 5.0.0-alpha.10 + bluebird-lst: 1.0.9 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + fs-extra: 10.1.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-ci: 3.0.1 + js-yaml: 4.1.1 + source-map-support: 0.5.21 + stat-mode: 1.0.0 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + + cac@6.7.14: {} + + cacache@16.1.3: + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001793: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@3.9.0: {} + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + optional: true + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clone@1.0.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + commander@5.1.0: {} + + compare-version@0.1.2: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + concat-map@0.0.1: {} + + conf@14.0.0: + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + atomically: 2.1.1 + debounce-fn: 6.0.0 + dot-prop: 9.0.0 + env-paths: 3.0.0 + json-schema-typed: 8.0.2 + semver: 7.8.1 + uint8array-extras: 1.5.0 + + config-file-ts@0.2.8-rc1: + dependencies: + glob: 10.4.5 + typescript: 5.9.3 + + console-control-strings@1.1.0: {} + + convert-source-map@2.0.0: {} + + core-util-is@1.0.2: + optional: true + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + crc@3.8.0: + dependencies: + buffer: 5.7.1 + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debounce-fn@6.0.0: + dependencies: + mimic-function: 5.0.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + detect-libc@2.1.2: {} + + detect-node@2.1.0: + optional: true + + didyoumean@1.2.2: {} + + dir-compare@4.2.0: + dependencies: + minimatch: 3.1.5 + p-limit: 3.1.0 + + dlv@1.1.3: {} + + dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + fs-extra: 10.1.0 + iconv-lite: 0.6.3 + js-yaml: 4.1.1 + optionalDependencies: + dmg-license: 1.0.11 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + dmg-license@1.0.11: + dependencies: + '@types/plist': 3.0.5 + '@types/verror': 1.10.11 + ajv: 6.15.0 + crc: 3.8.0 + iconv-corefoundation: 1.1.7 + plist: 3.1.1 + smart-buffer: 4.2.0 + verror: 1.10.1 + optional: true + + dot-prop@9.0.0: + dependencies: + type-fest: 4.41.0 + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.6.1 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + archiver: 5.3.2 + builder-util: 25.1.7 + fs-extra: 10.1.0 + transitivePeerDependencies: + - bluebird + - dmg-builder + - supports-color + + electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + fs-extra: 10.1.0 + is-ci: 3.0.1 + lazy-val: 1.0.5 + simple-update-notifier: 2.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + electron-publish@25.1.7: + dependencies: + '@types/fs-extra': 9.0.13 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + fs-extra: 10.1.0 + lazy-val: 1.0.5 + mime: 2.6.0 + transitivePeerDependencies: + - supports-color + + electron-store@10.1.0: + dependencies: + conf: 14.0.0 + type-fest: 4.41.0 + + electron-to-chromium@1.5.364: {} + + electron-vite@3.1.0(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) + cac: 6.7.14 + esbuild: 0.25.12 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 6.4.2(@types/node@25.9.1)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + electron@34.5.8: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.41 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + env-paths@3.0.0: {} + + err-code@2.0.3: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.1.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es6-error@4.1.1: + optional: true + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: + optional: true + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + exponential-backoff@3.1.3: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.4.1: + optional: true + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.1.2: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-extra@11.3.5: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.8.1 + serialize-error: 7.0.1 + optional: true + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + html-escaper@2.0.2: {} + + http-cache-semantics@4.2.0: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.1 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-corefoundation@1.1.7: + dependencies: + cli-truncate: 2.1.0 + node-addon-api: 1.7.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ip-address@10.2.0: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-lambda@1.0.1: {} + + is-number@7.0.0: {} + + is-unicode-supported@0.1.0: {} + + isarray@1.0.0: {} + + isbinaryfile@4.0.10: {} + + isbinaryfile@5.0.7: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + + jiti@1.21.7: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + lazy-val@1.0.5: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.flatten@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.union@4.6.0: {} + + lodash@4.18.1: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowercase-keys@2.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.1 + + make-fetch-happen@10.2.1: + dependencies: + agentkeepalive: 4.6.0 + cacache: 16.1.3 + http-cache-semantics: 4.2.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.15 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.1 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.3: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + negotiator@0.6.4: {} + + node-abi@3.92.0: + dependencies: + semver: 7.8.1 + + node-addon-api@1.7.2: + optional: true + + node-api-version@0.2.1: + dependencies: + semver: 7.8.1 + + node-gyp@9.4.1: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.8.1 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + + node-releases@2.0.46: {} + + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-keys@1.1.1: + optional: true + + obug@2.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + p-cancelable@2.1.1: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + pathe@2.0.3: {} + + pe-library@0.4.1: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + plist@3.1.1: + dependencies: + '@xmldom/xmldom': 0.9.10 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + postcss-import@15.1.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.15): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.15 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.15 + + postcss-nested@6.2.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-binary-file-arch@1.0.6: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resedit@1.7.2: + dependencies: + pe-library: 0.4.1 + + resolve-alpn@1.2.1: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sanitize-filename@1.6.4: + dependencies: + truncate-utf8-bytes: 1.0.2 + + sax@1.6.0: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver-compare@1.0.0: + optional: true + + semver@6.3.1: {} + + semver@7.8.1: {} + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + set-blocking@2.0.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.8.1 + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + optional: true + + smart-buffer@4.2.0: {} + + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.2.0 + smart-buffer: 4.2.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.1.3: + optional: true + + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + + stackback@0.0.2: {} + + stat-mode@1.0.0: {} + + std-env@4.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-import: 15.1.0(postcss@8.5.15) + postcss-js: 4.1.0(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) + postcss-nested: 6.2.0(postcss@8.5.15) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-file@3.4.0: + dependencies: + async-exit-hook: 2.0.1 + fs-extra: 10.1.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@1.2.3: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.7 + + tmp@0.2.7: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + + ts-interface-checker@0.1.13: {} + + type-fest@0.13.1: + optional: true + + type-fest@4.41.0: {} + + typescript@5.9.3: {} + + uint8array-extras@1.5.0: {} + + undici-types@6.21.0: {} + + undici-types@7.24.6: {} + + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + utf8-byte-length@1.0.5: {} + + util-deprecate@1.0.2: {} + + verror@1.10.1: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + optional: true + + vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.60.4 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.9.1 + fsevents: 2.3.3 + jiti: 1.21.7 + + vitest@4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)): + dependencies: + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@6.4.2(@types/node@25.9.1)(jiti@1.21.7)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.3 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 6.4.2(@types/node@25.9.1)(jiti@1.21.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.1 + '@vitest/coverage-v8': 4.1.7(vitest@4.1.7) + transitivePeerDependencies: + - msw + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + when-exit@2.1.5: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xmlbuilder@15.1.1: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 diff --git a/electron-app/src/__tests__/api.test.ts b/electron-app/src/__tests__/api.test.ts new file mode 100644 index 0000000..a880d64 --- /dev/null +++ b/electron-app/src/__tests__/api.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { getDaemonPort, setDaemonPort, getBaseUrl } from "../renderer/api"; + +describe("api", () => { + beforeEach(() => { + setDaemonPort(15801); + }); + + it("getDaemonPort returns default port", () => { + expect(getDaemonPort()).toBe(15801); + }); + + it("setDaemonPort updates port", () => { + setDaemonPort(15900); + expect(getDaemonPort()).toBe(15900); + }); + + it("getBaseUrl uses current port", () => { + expect(getBaseUrl()).toBe("http://127.0.0.1:15801"); + setDaemonPort(15900); + expect(getBaseUrl()).toBe("http://127.0.0.1:15900"); + }); +}); diff --git a/electron-app/src/main/daemon-bridge.ts b/electron-app/src/main/daemon-bridge.ts new file mode 100644 index 0000000..918cab3 --- /dev/null +++ b/electron-app/src/main/daemon-bridge.ts @@ -0,0 +1,99 @@ +import { spawn, ChildProcess } from "child_process"; +import { readFileSync, existsSync } from "fs"; +import { join, dirname } from "path"; +import { app } from "electron"; + +let daemonProcess: ChildProcess | null = null; + +interface DaemonInfo { + port: number; + pid: number; + started_at: string; +} + +function daemonJsonPath(): string { + const home = process.env.HOME || "/tmp"; + return join(home, ".sandbox", "daemon.json"); +} + +function readDaemonInfo(): DaemonInfo | null { + const path = daemonJsonPath(); + if (!existsSync(path)) return null; + try { + return JSON.parse(readFileSync(path, "utf-8")); + } catch { + return null; + } +} + +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +export function findRunningDaemon(): number | null { + const info = readDaemonInfo(); + if (!info) return null; + if (isProcessAlive(info.pid)) return info.port; + return null; +} + +function findDaemonBinary(): string { + // Dev mode: relative to project + const devPath = join(__dirname, "..", "..", "..", "target", "release", "sandbox-daemon"); + if (existsSync(devPath)) return devPath; + // Production: bundled in app resources + const prodPath = join(process.resourcesPath, "sandbox-daemon"); + if (existsSync(prodPath)) return prodPath; + // Same directory as electron binary + return join(dirname(app.getPath("exe")), "sandbox-daemon"); +} + +export async function ensureDaemon(): Promise { + const existingPort = findRunningDaemon(); + if (existingPort) return existingPort; + + const bin = findDaemonBinary(); + daemonProcess = spawn(bin, [], { + stdio: "pipe", + detached: false, + }); + + daemonProcess.stdout?.on("data", (data: Buffer) => { + console.log(`[daemon] ${data.toString().trim()}`); + }); + daemonProcess.stderr?.on("data", (data: Buffer) => { + console.error(`[daemon] ${data.toString().trim()}`); + }); + + // Wait for daemon.json to appear (up to 5s) + const port = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Daemon failed to start within 5s")); + }, 5000); + + const check = () => { + const info = readDaemonInfo(); + if (info && isProcessAlive(info.pid)) { + clearTimeout(timeout); + resolve(info.port); + } else { + setTimeout(check, 100); + } + }; + check(); + }); + + console.log(`Daemon started on port ${port}`); + return port; +} + +export function killDaemon() { + if (daemonProcess && !daemonProcess.killed) { + daemonProcess.kill(); + } +} diff --git a/electron-app/src/main/index.ts b/electron-app/src/main/index.ts new file mode 100644 index 0000000..13ca708 --- /dev/null +++ b/electron-app/src/main/index.ts @@ -0,0 +1,105 @@ +import { app, BrowserWindow, ipcMain } from "electron"; +import { join } from "path"; +import { writeFileSync, unlinkSync, mkdirSync } from "fs"; +import { ensureDaemon, killDaemon } from "./daemon-bridge"; + +const ELECTRON_JSON_PATH = join(process.env.HOME || "/tmp", ".sandbox", "electron.json"); + +function writeElectronJson(port: number) { + const dir = join(process.env.HOME || "/tmp", ".sandbox"); + mkdirSync(dir, { recursive: true }); + writeFileSync(ELECTRON_JSON_PATH, JSON.stringify({ pid: process.pid, port })); +} + +function removeElectronJson() { + try { unlinkSync(ELECTRON_JSON_PATH); } catch { /* ignore */ } +} + +let mainWindow: BrowserWindow | null = null; +let daemonPort: number | null = null; + +const gotTheLock = app.requestSingleInstanceLock(); + +if (!gotTheLock) { + app.quit(); +} else { + app.on("second-instance", () => { + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + } + }); + + app.whenReady().then(async () => { + try { + daemonPort = await ensureDaemon(); + } catch (err) { + console.error("Failed to start daemon:", err); + app.quit(); + return; + } + + writeElectronJson(daemonPort); + createWindow(); + }); +} + +// IPC: renderer asks for daemon port +ipcMain.handle("get-daemon-port", () => daemonPort); + +// IPC: unused stubs for compatibility +ipcMain.handle("create-tab", () => {}); +ipcMain.handle("switch-tab", () => {}); +ipcMain.handle("close-tab", () => {}); +ipcMain.handle("list-tabs", () => []); + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 600, + minHeight: 400, + title: "System Test Sandbox", + titleBarStyle: "hiddenInset", + vibrancy: "sidebar", + backgroundColor: "#1e1e1e", + show: false, + webPreferences: { + preload: join(__dirname, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + if (process.env.ELECTRON_RENDERER_URL) { + mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); + } else { + mainWindow.loadFile(join(__dirname, "../renderer/index.html")); + } + + mainWindow.once("ready-to-show", () => { + mainWindow?.show(); + }); + + mainWindow.on("closed", () => { + mainWindow = null; + }); +} + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + killDaemon(); + app.quit(); + } +}); + +app.on("before-quit", () => { + removeElectronJson(); + killDaemon(); +}); + +app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0 && daemonPort) { + createWindow(); + } +}); diff --git a/electron-app/src/main/tab-manager.ts b/electron-app/src/main/tab-manager.ts new file mode 100644 index 0000000..a6c7f44 --- /dev/null +++ b/electron-app/src/main/tab-manager.ts @@ -0,0 +1,124 @@ +import { BrowserWindow, WebContentsView } from "electron"; +import { join } from "path"; + +export interface SandboxTab { + id: string; + kind: "cli" | "app"; + title: string; + webContentsView: WebContentsView; +} + +const tabs: Map = new Map(); +let activeTabId: string | null = null; +let mainWindow: BrowserWindow | null = null; + +const TAB_BAR_HEIGHT = 36; +const TITLE_BAR_HEIGHT = 28; + +export function setMainWindow(win: BrowserWindow) { + mainWindow = win; +} + +export function createTab( + sandboxId: string, + kind: "cli" | "app", + title: string, + daemonPort: number, +): SandboxTab { + if (!mainWindow) throw new Error("No main window"); + + const view = new WebContentsView({ + webPreferences: { + preload: join(__dirname, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + // Load renderer page with sandbox params + const baseUrl = process.env.ELECTRON_RENDERER_URL + ? process.env.ELECTRON_RENDERER_URL + : `file://${join(__dirname, "../renderer/index.html")}`; + + const url = new URL(baseUrl); + url.searchParams.set("sandbox_id", sandboxId); + url.searchParams.set("kind", kind); + url.searchParams.set("title", title); + url.searchParams.set("daemon_port", daemonPort.toString()); + view.webContents.loadURL(url.toString()); + + const tab: SandboxTab = { + id: sandboxId, + kind, + title, + webContentsView: view, + }; + + tabs.set(sandboxId, tab); + + // If first tab, activate immediately; otherwise position off-screen + if (tabs.size === 1) { + switchToTab(sandboxId); + } else { + positionViewOffScreen(view); + } + + mainWindow.contentView.addChildView(view); + return tab; +} + +export function switchToTab(targetId: string) { + if (!mainWindow) return; + const target = tabs.get(targetId); + if (!target) return; + + const { width, height } = mainWindow.getContentBounds(); + const topOffset = TAB_BAR_HEIGHT + TITLE_BAR_HEIGHT; + + // Move all tabs off-screen except target + for (const [id, tab] of tabs) { + if (id === targetId) { + tab.webContentsView.setBounds({ + x: 0, + y: topOffset, + width, + height: height - topOffset, + }); + } else { + positionViewOffScreen(tab.webContentsView); + } + } + + activeTabId = targetId; +} + +export function closeTab(sandboxId: string) { + const tab = tabs.get(sandboxId); + if (!tab) return; + + mainWindow?.contentView.removeChildView(tab.webContentsView); + tab.webContentsView.webContents.close(); + tabs.delete(sandboxId); + + // If closed active tab, switch to another + if (activeTabId === sandboxId) { + const remaining = Array.from(tabs.keys()); + if (remaining.length > 0) { + switchToTab(remaining[0]); + } else { + activeTabId = null; + } + } +} + +export function getActiveTabId(): string | null { + return activeTabId; +} + +export function getAllTabs(): SandboxTab[] { + return Array.from(tabs.values()); +} + +function positionViewOffScreen(view: WebContentsView) { + view.setBounds({ x: -15000, y: -15000, width: 1200, height: 800 }); +} diff --git a/electron-app/src/preload/index.ts b/electron-app/src/preload/index.ts new file mode 100644 index 0000000..318bcff --- /dev/null +++ b/electron-app/src/preload/index.ts @@ -0,0 +1,10 @@ +import { contextBridge, ipcRenderer } from "electron"; + +contextBridge.exposeInMainWorld("sandbox", { + getDaemonPort: () => ipcRenderer.invoke("get-daemon-port"), + createTab: (sandboxId: string, kind: string, title: string) => + ipcRenderer.invoke("create-tab", sandboxId, kind, title), + switchTab: (sandboxId: string) => ipcRenderer.invoke("switch-tab", sandboxId), + closeTab: (sandboxId: string) => ipcRenderer.invoke("close-tab", sandboxId), + listTabs: () => ipcRenderer.invoke("list-tabs"), +}); diff --git a/electron-app/src/renderer/api.ts b/electron-app/src/renderer/api.ts new file mode 100644 index 0000000..42e116e --- /dev/null +++ b/electron-app/src/renderer/api.ts @@ -0,0 +1,112 @@ +/** + * Daemon API client for Electron renderer. + * Connects to sandbox-daemon HTTP/WebSocket API. + */ + +let _port = 15801; + +export function getDaemonPort(): number { + return _port; +} + +export function setDaemonPort(port: number) { + _port = port; +} + +export function getBaseUrl(): string { + return `http://127.0.0.1:${_port}`; +} + +export interface SandboxInfo { + id: string; + kind: { type: string; detail: { command: string; args: string[] } }; + status: { type: string }; + pty_pid: number | null; + port: number; +} + +export async function fetchSandboxList(): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/list`); + return res.json(); +} + +export async function fetchSandboxInfo(id: string): Promise { + const list = await fetchSandboxList(); + return list.find((sb) => sb.id === id); +} + +export function connectPty(sandboxId: string, ptyPid: number): PtyConnection { + const ws = new WebSocket(`ws://127.0.0.1:${_port}/sandbox/${sandboxId}/pty/ws/${ptyPid}`); + ws.binaryType = "arraybuffer"; + const outputListeners: ((data: string | Uint8Array) => void)[] = []; + + ws.onmessage = (e) => { + if (e.data instanceof ArrayBuffer) { + for (const cb of outputListeners) cb(new Uint8Array(e.data)); + } else if (typeof e.data === "string") { + for (const cb of outputListeners) cb(e.data); + } + }; + + return { + onOutput(cb) { + outputListeners.push(cb); + return () => { + const idx = outputListeners.indexOf(cb); + if (idx >= 0) outputListeners.splice(idx, 1); + }; + }, + sendInput(data) { + if (ws.readyState === WebSocket.OPEN) ws.send(data); + }, + resize(cols, rows) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: "resize", cols, rows })); + } + }, + close() { + ws.close(); + }, + }; +} + +export async function createSandbox( + mode: "cli" | "app", + command: string, + args: string[] = [] +): Promise<{ sandbox_id: string; pty_pid: number | null; window_id: number | null }> { + const res = await fetch(`${getBaseUrl()}/sandbox/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mode, command, args }), + }); + if (!res.ok) throw new Error(`Create failed: ${res.status}`); + return res.json(); +} + +export async function takeScreenshot(sandboxId: string): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/screenshot`); + if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`); + return res.blob(); +} + +export async function closeSandbox(sandboxId: string): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/close`, { method: "POST" }); + if (!res.ok) throw new Error(`Close failed: ${res.status}`); +} + +export async function setWindowId(sandboxId: string, windowId: number): Promise { + const res = await fetch(`${getBaseUrl()}/sandbox/${sandboxId}/window`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ window_id: windowId }), + }); + if (!res.ok) throw new Error(`Set window_id failed: ${res.status}`); +} + +export interface PtyConnection { + onOutput: (cb: (data: string | Uint8Array) => void) => () => void; + sendInput: (data: string) => void; + resize: (cols: number, rows: number) => void; + close: () => void; +} diff --git a/electron-app/src/renderer/components/AppPanel.tsx b/electron-app/src/renderer/components/AppPanel.tsx new file mode 100644 index 0000000..e209f20 --- /dev/null +++ b/electron-app/src/renderer/components/AppPanel.tsx @@ -0,0 +1,52 @@ +import { useState, useEffect, useCallback } from "react"; +import { takeScreenshot } from "../api"; + +interface AppPanelProps { + sandboxId: string; +} + +export default function AppPanel({ sandboxId }: AppPanelProps) { + const [screenshotUrl, setScreenshotUrl] = useState(null); + const [loading, setLoading] = useState(false); + + const refreshScreenshot = useCallback(async () => { + setLoading(true); + try { + const blob = await takeScreenshot(sandboxId); + const url = URL.createObjectURL(blob); + setScreenshotUrl((prev) => { + if (prev) URL.revokeObjectURL(prev); + return url; + }); + } catch (e) { + console.error("Screenshot failed:", e); + } finally { + setLoading(false); + } + }, [sandboxId]); + + useEffect(() => { + refreshScreenshot(); + const interval = setInterval(refreshScreenshot, 5000); + return () => { + clearInterval(interval); + }; + }, [sandboxId, refreshScreenshot]); + + return ( +
+ {screenshotUrl ? ( + App screenshot + ) : ( +
+ {loading ? "Loading screenshot..." : "No screenshot available"} +
+ )} +
+ +
+
+ ); +} diff --git a/electron-app/src/renderer/components/TabBar.tsx b/electron-app/src/renderer/components/TabBar.tsx new file mode 100644 index 0000000..44a67f6 --- /dev/null +++ b/electron-app/src/renderer/components/TabBar.tsx @@ -0,0 +1,18 @@ +interface TabBarProps { + activeTabId: string | null; + onRefresh?: () => void; +} + +export default function TabBar({ activeTabId, onRefresh }: TabBarProps) { + return ( +
+
+ +
+ ); +} diff --git a/electron-app/src/renderer/components/Terminal.tsx b/electron-app/src/renderer/components/Terminal.tsx new file mode 100644 index 0000000..2924879 --- /dev/null +++ b/electron-app/src/renderer/components/Terminal.tsx @@ -0,0 +1,130 @@ +import { useEffect, useRef, useCallback } from "react"; +import { Terminal } from "@xterm/xterm"; +import { FitAddon } from "@xterm/addon-fit"; +import { connectPty } from "../api"; +import "@xterm/xterm/css/xterm.css"; + +interface TerminalProps { + sandboxId: string; + ptyPid: number; + onReady?: (cols: number, rows: number) => void; +} + +export default function SandboxTerminal({ sandboxId, ptyPid, onReady }: TerminalProps) { + const terminalRef = useRef(null); + const xtermRef = useRef(null); + const fitAddonRef = useRef(null); + const connRef = useRef | null>(null); + + // Initialize xterm.js + useEffect(() => { + if (!terminalRef.current) return; + if (xtermRef.current) return; + + const term = new Terminal({ + cursorBlink: true, + cursorStyle: "bar", + fontSize: 13, + fontFamily: '"SF Mono", "Menlo", "Monaco", monospace', + fontWeight: "400", + fontWeightBold: "600", + scrollback: 10000, + lineHeight: 1.4, + letterSpacing: 0, + theme: { + background: "#1a1a1a", + foreground: "#cccccc", + cursor: "#ffffff", + cursorAccent: "#1a1a1a", + selectionBackground: "#264f78", + selectionForeground: "#ffffff", + black: "#1a1a1a", + red: "#ff6b6b", + green: "#69db7c", + yellow: "#ffd43b", + blue: "#74c0fc", + magenta: "#da77f2", + cyan: "#66d9e8", + white: "#cccccc", + brightBlack: "#666666", + brightRed: "#ff8787", + brightGreen: "#8ce99a", + brightYellow: "#ffe066", + brightBlue: "#a5d8ff", + brightMagenta: "#e599f7", + brightCyan: "#99e9f2", + brightWhite: "#ffffff", + }, + allowTransparency: true, + }); + + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.open(terminalRef.current); + + // Fit after a small delay to ensure DOM is ready + requestAnimationFrame(() => { + fitAddon.fit(); + onReady?.(term.cols, term.rows); + }); + + term.onData((data) => { + connRef.current?.sendInput(data); + }); + + const handleResize = () => { + fitAddon.fit(); + connRef.current?.resize(term.cols, term.rows); + }; + window.addEventListener("resize", handleResize); + + xtermRef.current = term; + fitAddonRef.current = fitAddon; + + return () => { + window.removeEventListener("resize", handleResize); + term.dispose(); + xtermRef.current = null; + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Connect to PTY WebSocket + useEffect(() => { + connRef.current?.close(); + connRef.current = null; + + const conn = connectPty(sandboxId, ptyPid); + connRef.current = conn; + + const decoder = new TextDecoder(); + conn.onOutput((data) => { + const term = xtermRef.current; + if (!term) return; + const writeData = typeof data === "string" ? data : decoder.decode(data as Uint8Array); + term.write(writeData); + }); + + // Send initial resize + const term = xtermRef.current; + if (term) { + conn.resize(term.cols, term.rows); + } + + return () => { + conn.close(); + connRef.current = null; + }; + }, [sandboxId, ptyPid]); + + const containerRef = useCallback((node: HTMLDivElement | null) => { + if (node) { + requestAnimationFrame(() => fitAddonRef.current?.fit()); + } + }, []); + + return ( +
+
+
+ ); +} diff --git a/electron-app/src/renderer/index.html b/electron-app/src/renderer/index.html new file mode 100644 index 0000000..2fa61a2 --- /dev/null +++ b/electron-app/src/renderer/index.html @@ -0,0 +1,12 @@ + + + + + + System Test Sandbox + + +
+ + + diff --git a/electron-app/src/renderer/main.tsx b/electron-app/src/renderer/main.tsx new file mode 100644 index 0000000..eefaaf9 --- /dev/null +++ b/electron-app/src/renderer/main.tsx @@ -0,0 +1,278 @@ +import { useState, useEffect, useCallback, useRef } from "react"; +import ReactDOM from "react-dom/client"; +import SandboxTerminal from "./components/Terminal"; +import { + SandboxInfo, + fetchSandboxList, + setDaemonPort, + getDaemonPort, + createSandbox, +} from "./api"; +import AppPanel from "./components/AppPanel"; +import "./styles.css"; + +declare global { + interface Window { + sandbox: { + getDaemonPort: () => Promise; + createTab: (sandboxId: string, kind: string, title: string) => Promise; + switchTab: (sandboxId: string) => Promise; + closeTab: (sandboxId: string) => Promise; + listTabs: () => Promise<{ id: string; kind: string; title: string }[]>; + }; + } +} + +interface Tab { + id: string; + kind: string; + title: string; + sandbox: SandboxInfo; +} + +type Theme = "dark" | "light" | "system"; + +function App() { + const [tabs, setTabs] = useState([]); + const [activeTabId, setActiveTabId] = useState(null); + const [theme, setTheme] = useState(() => { + return (localStorage.getItem("theme") as Theme) || "system"; + }); + const [connected, setConnected] = useState(false); + const [showNewDialog, setShowNewDialog] = useState(false); + const [newSandboxCmd, setNewSandboxCmd] = useState(""); + const [newSandboxMode, setNewSandboxMode] = useState<"cli" | "app">("cli"); + const refreshTimer = useRef>(); + + // Apply theme + useEffect(() => { + const root = document.documentElement; + root.classList.remove("dark", "light"); + if (theme === "system") { + // Let CSS media query handle it + } else { + root.classList.add(theme); + } + localStorage.setItem("theme", theme); + }, [theme]); + + // Initialize daemon port and load sandboxes + useEffect(() => { + window.sandbox.getDaemonPort().then((port) => { + setDaemonPort(port); + setConnected(true); + refreshSandboxes(); + }); + }, []); + + // Poll for sandbox changes + const refreshSandboxes = useCallback(async () => { + try { + const list = await fetchSandboxList(); + setTabs((prev) => { + const existing = new Map(prev.map((t) => [t.id, t])); + const next: Tab[] = []; + for (const sb of list) { + const title = sb.kind?.detail?.command || sb.id.slice(0, 8); + const existingTab = existing.get(sb.id); + next.push({ + id: sb.id, + kind: sb.kind?.type || "cli", + title, + sandbox: sb, + }); + } + return next; + }); + + // Auto-select first tab if none selected + if (!activeTabId && list.length > 0) { + setActiveTabId(list[0].id); + } + } catch { + setConnected(false); + } + }, [activeTabId]); + + // Periodic refresh + useEffect(() => { + refreshTimer.current = setInterval(refreshSandboxes, 3000); + return () => { + if (refreshTimer.current) clearInterval(refreshTimer.current); + }; + }, [refreshSandboxes]); + + const handleCloseTab = useCallback( + async (id: string) => { + try { + await fetch(`${getDaemonPort() ? `http://127.0.0.1:${getDaemonPort()}` : ""}/sandbox/${id}`, { + method: "DELETE", + }); + } catch { + // ignore + } + setTabs((prev) => prev.filter((t) => t.id !== id)); + if (activeTabId === id) { + const remaining = tabs.filter((t) => t.id !== id); + setActiveTabId(remaining.length > 0 ? remaining[0].id : null); + } + }, + [activeTabId, tabs] + ); + + const handleTabClick = useCallback((id: string) => { + setActiveTabId(id); + }, []); + + const toggleTheme = useCallback(() => { + setTheme((prev) => { + if (prev === "dark") return "light"; + if (prev === "light") return "system"; + return "dark"; + }); + }, []); + + const activeTab = tabs.find((t) => t.id === activeTabId); + + return ( +
+ {/* Title Bar */} +
+
+
+ System Test Sandbox +
+
+ +
+
+ + {/* Tab Bar */} +
+ {tabs.map((tab) => ( + + + ))} + +
+ + {/* Terminal Area */} + {activeTab ? ( + activeTab.kind === "app" ? ( +
+ +
+ ) : ( +
+ +
+ ) + ) : ( +
+
+
No sandbox open
+
+ Run sandbox start in your terminal to get started +
+
+ )} + + {/* Status Bar */} +
+
+
+ {connected ? `Daemon :${getDaemonPort()}` : "Disconnected"} +
+
+ {tabs.length} sandbox{tabs.length !== 1 ? "es" : ""} +
+ {activeTab && ( +
+ PTY PID: {activeTab.sandbox.pty_pid} +
+ )} +
+
+ {theme === "system" ? "Auto" : theme === "dark" ? "Dark" : "Light"} +
+
+ + {/* New Sandbox Dialog */} + {showNewDialog && ( +
setShowNewDialog(false)}> +
e.stopPropagation()}> +
New Sandbox
+
+ + +
+
+ + setNewSandboxCmd(e.target.value)} + placeholder={newSandboxMode === "cli" ? "zsh" : "/Applications/TextEdit.app"} + autoFocus + /> +
+
+ + +
+
+
+ )} +
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")!).render(); diff --git a/electron-app/src/renderer/styles.css b/electron-app/src/renderer/styles.css new file mode 100644 index 0000000..482b62d --- /dev/null +++ b/electron-app/src/renderer/styles.css @@ -0,0 +1,513 @@ +/* System Test Sandbox — macOS Native Theme */ + +:root { + /* Dark mode (default) */ + --bg-primary: #1e1e1e; + --bg-secondary: #252525; + --bg-tertiary: #2d2d2d; + --bg-hover: #353535; + --bg-active: #3a3a3a; + --bg-terminal: #1a1a1a; + + --text-primary: #ffffff; + --text-secondary: #999999; + --text-tertiary: #666666; + + --border-primary: #3a3a3a; + --border-secondary: #2a2a2a; + + --accent: #007aff; + --accent-hover: #0066d6; + --accent-text: #ffffff; + + --success: #34c759; + --warning: #ff9500; + --error: #ff3b30; + + --tab-bg: #2d2d2d; + --tab-bg-active: #1e1e1e; + --tab-text: #999999; + --tab-text-active: #ffffff; + --tab-border: #3a3a3a; + + --titlebar-bg: #2d2d2d; + --titlebar-text: #ffffff; + + --statusbar-bg: #252525; + --statusbar-text: #999999; + + --traffic-light-red: #ff5f56; + --traffic-light-yellow: #ffbd2e; + --traffic-light-green: #27c93f; + + color-scheme: dark; +} + +:root.light { + --bg-primary: #ffffff; + --bg-secondary: #f5f5f5; + --bg-tertiary: #e5e5e5; + --bg-hover: #eeeeee; + --bg-active: #e0e0e0; + --bg-terminal: #fafafa; + + --text-primary: #1a1a1a; + --text-secondary: #666666; + --text-tertiary: #999999; + + --border-primary: #d0d0d0; + --border-secondary: #e5e5e5; + + --accent: #007aff; + --accent-hover: #0066d6; + --accent-text: #ffffff; + + --success: #34c759; + --warning: #ff9500; + --error: #ff3b30; + + --tab-bg: #f0f0f0; + --tab-bg-active: #ffffff; + --tab-text: #666666; + --tab-text-active: #1a1a1a; + --tab-border: #d0d0d0; + + --titlebar-bg: #f5f5f5; + --titlebar-text: #1a1a1a; + + --statusbar-bg: #f0f0f0; + --statusbar-text: #666666; + + color-scheme: light; +} + +@media (prefers-color-scheme: light) { + :root:not(.dark) { + --bg-primary: #ffffff; + --bg-secondary: #f5f5f5; + --bg-tertiary: #e5e5e5; + --bg-hover: #eeeeee; + --bg-active: #e0e0e0; + --bg-terminal: #fafafa; + + --text-primary: #1a1a1a; + --text-secondary: #666666; + --text-tertiary: #999999; + + --border-primary: #d0d0d0; + --border-secondary: #e5e5e5; + + --tab-bg: #f0f0f0; + --tab-bg-active: #ffffff; + --tab-text: #666666; + --tab-text-active: #1a1a1a; + --tab-border: #d0d0d0; + + --titlebar-bg: #f5f5f5; + --titlebar-text: #1a1a1a; + + --statusbar-bg: #f0f0f0; + --statusbar-text: #666666; + + color-scheme: light; + } +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #root { + height: 100%; + width: 100%; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif; + background: var(--bg-primary); + color: var(--text-primary); +} + +/* Title Bar */ +.titlebar { + -webkit-app-region: drag; + height: 38px; + background: var(--titlebar-bg); + border-bottom: 1px solid var(--border-primary); + display: flex; + align-items: center; + padding: 0 12px; + user-select: none; +} + +.titlebar-traffic-lights { + width: 68px; + flex-shrink: 0; +} + +.titlebar-content { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.titlebar-title { + font-size: 13px; + font-weight: 500; + color: var(--titlebar-text); + opacity: 0.8; +} + +.titlebar-actions { + -webkit-app-region: no-drag; + display: flex; + align-items: center; + gap: 6px; + width: 68px; + justify-content: flex-end; +} + +/* Tab Bar */ +.tab-bar { + height: 36px; + background: var(--tab-bg); + border-bottom: 1px solid var(--border-primary); + display: flex; + align-items: flex-end; + padding: 0 8px; + gap: 1px; + overflow-x: auto; + scrollbar-width: none; +} + +.tab-bar::-webkit-scrollbar { + display: none; +} + +.tab-item { + -webkit-app-region: no-drag; + height: 30px; + padding: 0 16px; + display: flex; + align-items: center; + gap: 8px; + background: transparent; + border: none; + border-radius: 6px 6px 0 0; + color: var(--tab-text); + font-size: 12px; + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: all 0.15s ease; + position: relative; +} + +.tab-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.tab-item.active { + background: var(--tab-bg-active); + color: var(--tab-text-active); + border: 1px solid var(--tab-border); + border-bottom: 1px solid var(--tab-bg-active); + margin-bottom: -1px; +} + +.tab-item .tab-icon { + font-size: 14px; +} + +.tab-item .tab-close { + -webkit-app-region: no-drag; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: transparent; + border: none; + color: var(--text-tertiary); + cursor: pointer; + font-size: 12px; + opacity: 0; + transition: all 0.15s ease; +} + +.tab-item:hover .tab-close, +.tab-item.active .tab-close { + opacity: 1; +} + +.tab-item .tab-close:hover { + background: var(--bg-active); + color: var(--text-primary); +} + +.tab-add { + -webkit-app-region: no-drag; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: transparent; + border: none; + color: var(--text-tertiary); + cursor: pointer; + font-size: 16px; + transition: all 0.15s ease; + margin-left: 4px; +} + +.tab-add:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +/* Main Content */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.terminal-container { + flex: 1; + background: var(--bg-terminal); + overflow: hidden; +} + +.terminal-container .xterm { + padding: 8px; +} + +/* Status Bar */ +.statusbar { + height: 24px; + background: var(--statusbar-bg); + border-top: 1px solid var(--border-primary); + display: flex; + align-items: center; + padding: 0 12px; + font-size: 11px; + color: var(--statusbar-text); + gap: 16px; +} + +.statusbar-item { + display: flex; + align-items: center; + gap: 4px; +} + +.statusbar-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--success); +} + +.statusbar-dot.warning { + background: var(--warning); +} + +.statusbar-dot.error { + background: var(--error); +} + +.statusbar-spacer { + flex: 1; +} + +/* Empty State */ +.empty-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + color: var(--text-tertiary); +} + +.empty-state-icon { + font-size: 48px; + opacity: 0.5; +} + +.empty-state-text { + font-size: 14px; +} + +.empty-state-hint { + font-size: 12px; + color: var(--text-tertiary); + opacity: 0.7; +} + +/* Loading State */ +.loading { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); + font-size: 14px; +} + +.loading-spinner { + width: 20px; + height: 20px; + border: 2px solid var(--border-primary); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-right: 8px; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Theme Toggle Button */ +.theme-toggle { + -webkit-app-region: no-drag; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + transition: all 0.15s ease; +} + +.theme-toggle:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +/* ── Dialog ──────────────────────────────────── */ + +.dialog-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.dialog { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 24px; + min-width: 360px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.dialog-title { + font-size: 15px; + font-weight: 600; + margin-bottom: 16px; +} + +.dialog-field { + margin-bottom: 12px; +} + +.dialog-field label { + display: block; + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.dialog-field input, +.dialog-field select { + width: 100%; + padding: 6px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-primary); + font-size: 13px; + font-family: inherit; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; +} + +.dialog-actions button { + padding: 6px 16px; + border-radius: 6px; + border: 1px solid var(--border-color); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; +} + +.dialog-actions button.primary { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +/* ── App Panel ───────────────────────────────── */ + +.app-panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--bg-secondary); +} + +.app-screenshot { + flex: 1; + object-fit: contain; + padding: 8px; +} + +.app-placeholder { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + font-size: 14px; +} + +.app-controls { + padding: 8px 12px; + border-top: 1px solid var(--border-color); + display: flex; + gap: 8px; +} + +.app-controls button { + padding: 4px 12px; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + cursor: pointer; +} diff --git a/electron-app/tsconfig.json b/electron-app/tsconfig.json new file mode 100644 index 0000000..487e008 --- /dev/null +++ b/electron-app/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src/renderer/**/*"] +} diff --git a/electron-app/tsconfig.node.json b/electron-app/tsconfig.node.json new file mode 100644 index 0000000..5b2d148 --- /dev/null +++ b/electron-app/tsconfig.node.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/main/**/*", "src/preload/**/*", "vite.config.ts"] +} diff --git a/electron-app/vite.config.ts b/electron-app/vite.config.ts new file mode 100644 index 0000000..a85df80 --- /dev/null +++ b/electron-app/vite.config.ts @@ -0,0 +1,20 @@ +import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin } from "electron-vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + }, + preload: { + plugins: [externalizeDepsPlugin()], + }, + renderer: { + plugins: [react()], + resolve: { + alias: { + "@": resolve("src/renderer"), + }, + }, + }, +}); diff --git a/electron-app/vitest.config.ts b/electron-app/vitest.config.ts new file mode 100644 index 0000000..81955b1 --- /dev/null +++ b/electron-app/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/**/*.{test,spec}.{ts,tsx}"], + environment: "node", + }, +}); diff --git a/release.sh b/release.sh index e2fe3c1..f284083 100755 --- a/release.sh +++ b/release.sh @@ -4,7 +4,7 @@ set -euo pipefail # ============================================================ # system-test-sandbox — Release Build Script # ============================================================ -# Builds the Tauri sandbox app + CLI binary and packages +# Builds the Electron sandbox app + CLI binary and packages # them into ./release/. # # Prerequisites: @@ -49,43 +49,71 @@ ok "All prerequisites met" # --- step 2: clean up old processes & registries --- echo "" info "Cleaning up old sandbox processes..." -pkill -f "system-test-sandbox" 2>/dev/null || true -pkill -f "sandbox-cli" 2>/dev/null || true -pkill -x "sandbox" 2>/dev/null || true -rm -f ~/.sandbox/instances/*.json 2>/dev/null || true -ok "Cleanup done" -# --- step 3: build frontend --- -echo "" -info "Building frontend (sandbox-web)..." -cd "$SCRIPT_DIR/sandbox-web" -pnpm install --silent 2>&1 | tail -1 -pnpm build 2>&1 | tail -5 -ok "Frontend built" +# Kill daemon by PID from daemon.json (avoid pkill -f which matches Electron apps) +if [ -f ~/.sandbox/daemon.json ]; then + DAEMON_PID=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('pid',''))" ~/.sandbox/daemon.json 2>/dev/null) + if [ -n "$DAEMON_PID" ] && kill -0 "$DAEMON_PID" 2>/dev/null; then + kill "$DAEMON_PID" 2>/dev/null || true + info "Stopped daemon (PID $DAEMON_PID)" + fi + rm -f ~/.sandbox/daemon.json +fi -# --- step 4: build Tauri app (includes Rust build) --- -echo "" -info "Building Tauri sandbox app..." -cd "$SCRIPT_DIR" -cargo tauri build 2>&1 | tail -10 +# Kill Electron app by exact process name +pkill -x "System Test Sandbox" 2>/dev/null || true -TAURI_BUNDLE="$SCRIPT_DIR/target/release/bundle/macos/${APP_NAME}.app" -if [ ! -d "$TAURI_BUNDLE" ]; then - err "Tauri app bundle not found at $TAURI_BUNDLE" -fi -ok "Tauri app built: $(du -sh "$TAURI_BUNDLE" | cut -f1)" +# Kill CLI processes by exact binary name +pkill -x "sandbox" 2>/dev/null || true +pkill -x "sandbox-daemon" 2>/dev/null || true -# --- step 5: build CLI binary (release) --- +rm -f ~/.sandbox/instances/*.json 2>/dev/null || true +ok "Cleanup done" + +# --- step 3: build CLI + daemon binaries (release) --- echo "" -info "Building CLI binary (release)..." -cargo build --release -p sandbox-cli +info "Building CLI + daemon binaries (release)..." +cargo build --release -p sandbox-cli -p sandbox-daemon CLI_BIN="$SCRIPT_DIR/target/release/sandbox" +DAEMON_BIN="$SCRIPT_DIR/target/release/sandbox-daemon" if [ ! -f "$CLI_BIN" ]; then err "CLI binary not found at $CLI_BIN" fi +if [ ! -f "$DAEMON_BIN" ]; then + err "Daemon binary not found at $DAEMON_BIN" +fi ok "CLI binary built: $(du -h "$CLI_BIN" | cut -f1)" +ok "Daemon binary built: $(du -h "$DAEMON_BIN" | cut -f1)" -# --- step 6: assemble release folder --- +# --- step 4: build Electron app --- +echo "" +info "Building Electron app..." +cd "$SCRIPT_DIR/electron-app" +pnpm install --silent 2>&1 | tail -1 +pnpm build 2>&1 | tail -5 + +info "Packaging Electron app..." +ELECTRON_MIRROR="${ELECTRON_MIRROR:-https://npmmirror.com/mirrors/electron/}" pnpm run pack 2>&1 | tail -10 + +# Find the built .app bundle +ELECTRON_BUNDLE="" +for dir in \ + "$SCRIPT_DIR/electron-app/dist/electron/mac-arm64/${APP_NAME}.app" \ + "$SCRIPT_DIR/electron-app/dist/electron/mac/${APP_NAME}.app" \ + "$SCRIPT_DIR/dist/electron/mac-arm64/${APP_NAME}.app" \ + "$SCRIPT_DIR/dist/electron/mac/${APP_NAME}.app"; do + if [ -d "$dir" ]; then + ELECTRON_BUNDLE="$dir" + break + fi +done + +if [ -z "$ELECTRON_BUNDLE" ]; then + err "Electron app bundle not found. Check electron-builder output." +fi +ok "Electron app built: $(du -sh "$ELECTRON_BUNDLE" | cut -f1)" + +# --- step 5: assemble release folder --- echo "" info "Assembling release artifacts -> $RELEASE_DIR" rm -rf "$RELEASE_DIR" @@ -94,13 +122,20 @@ mkdir -p "$RELEASE_DIR" # Copy CLI cp "$CLI_BIN" "$RELEASE_DIR/sandbox" chmod +x "$RELEASE_DIR/sandbox" +codesign --force --sign - "$RELEASE_DIR/sandbox" 2>/dev/null || true ok "sandbox CLI binary" -# Copy Tauri app bundle -cp -R "$TAURI_BUNDLE" "$RELEASE_DIR/${APP_NAME}.app" -ok "${APP_NAME}.app bundle" +# Copy daemon (standalone copy for CLI to discover) +cp "$DAEMON_BIN" "$RELEASE_DIR/sandbox-daemon" +chmod +x "$RELEASE_DIR/sandbox-daemon" +codesign --force --sign - "$RELEASE_DIR/sandbox-daemon" 2>/dev/null || true +ok "sandbox-daemon binary" -# --- step 7: generate README --- +# Copy Electron app bundle +cp -R "$ELECTRON_BUNDLE" "$RELEASE_DIR/${APP_NAME}.app" +ok "${APP_NAME}.app bundle (Electron)" + +# --- step 6: generate README --- echo "" info "Generating README.md..." @@ -109,14 +144,15 @@ BUILD_DATE="$(date '+%Y-%m-%d %H:%M')" cat > "$RELEASE_DIR/README.md" << 'RELEASEREADME' # System Test Sandbox — Release v${VERSION} -macOS 桌面自动化沙箱。通过 CLI 启动 Tauri 沙箱窗口,内置 xterm.js 终端运行命令行工具(如 Claude Code),支持截图和输入模拟。 +macOS 桌面自动化沙箱。通过 CLI 启动 Electron 沙箱窗口,内置 xterm.js 终端运行命令行工具(如 Claude Code),支持截图和输入模拟。 ## 文件说明 ``` release/ ├── sandbox # CLI 工具(命令行入口) -├── System Test Sandbox.app/ # Tauri 沙箱 macOS 应用 +├── sandbox-daemon # 守护进程(CLI 自动管理) +├── System Test Sandbox.app/ # Electron 沙箱 macOS 应用 └── README.md # 本文件 ``` @@ -163,45 +199,43 @@ release/ ### 截图 \`\`\`bash -# 自动发现沙箱窗口并截图(保存为 PNG) -./sandbox screenshot -o screenshot.png - -# 指定窗口 ID 截图 -./sandbox screenshot --window-id 12345 -o screenshot.png +# 截取指定沙箱窗口 +./sandbox screenshot --id -o screenshot.png \`\`\` ### 其他命令 \`\`\`bash -# 列出所有可见窗口 -./sandbox windows +# 列出所有沙箱 +./sandbox list + +# 查看沙箱详情 +./sandbox inspect # 关闭沙箱 -./sandbox shutdown +./sandbox close \`\`\` ### 示例工作流 \`\`\`bash -# 1. 启动 Claude Code +# 1. 启动 Claude Code(自动打开 Electron 窗口) ./sandbox start claude # 2. 等待 Claude 启动(约 10 秒) sleep 10 # 3. 截图查看状态 -./sandbox screenshot -o screenshot.png +./sandbox screenshot --id -o screenshot.png -# 4. 关闭沙箱 -./sandbox shutdown -\`\`\` +# 4. 启动另一个沙箱(自动创建新 Tab) +./sandbox start zsh -\`\`\`bash -# 非交互式快速提问 -./sandbox start claude -- -p "用 Python 写一个 hello world" -sleep 30 -./sandbox screenshot -o claude_response.png -./sandbox shutdown +# 5. 列出所有沙箱 +./sandbox list + +# 6. 关闭指定沙箱 +./sandbox close \`\`\` ## 三、架构 @@ -211,21 +245,24 @@ sandbox start claude │ ▼ CLI (sandbox) - │ spawn System Test Sandbox.app --mode=cli --cmd=claude + │ 1. 启动 sandbox-daemon(如未运行) + │ 2. 通过 HTTP 创建沙箱 + │ 3. 启动 Electron 窗口(如未运行) ▼ -Tauri 沙箱窗口 - ┌────────────────────────────────────────────┐ - │ 终端面板 (xterm.js) │ Screenshot Preview │ - │ ← Claude 运行在这里 │ │ - ├────────────────────────────────────────────┤ - │ Control Panel: Screenshot / Spawn / Click │ - ├────────────────────────────────────────────┤ - │ Status: Server :5801 | Processes: X | ... │ - └────────────────────────────────────────────┘ - │ HTTP :5801 +sandbox-daemon (HTTP :15801) + - 管理 PTY 进程 + - 提供截图/输入 API + - WebSocket PTY 终端 + │ ▼ - 内嵌 HTTP API (axum) - - /screenshot, /input/click, /pty/write, ... +Electron 窗口 (Chromium) + ┌────────────────────────────────────┐ + │ Tab: claude Tab: zsh Tab: ... │ + ├────────────────────────────────────┤ + │ xterm.js 终端 │ + │ ← PTY WebSocket 连接 │ + │ 标准 term.write() 渲染 │ + └────────────────────────────────────┘ \`\`\` ## 四、常见问题 @@ -240,7 +277,7 @@ A: 检查「辅助功能」权限是否已授予。 A: 确保 \`System Test Sandbox.app\` 与 \`sandbox\` 在同一目录下。 **Q: 沙箱窗口内终端空白?** -A: 等待几秒让 Claude 启动,终端会自动连接 PTY 输出。 +A: 等待几秒让 CLI 工具启动,终端会自动连接 PTY 输出。 --- diff --git a/release/README.md b/release/README.md index 5b63c14..888979d 100644 --- a/release/README.md +++ b/release/README.md @@ -1,13 +1,14 @@ # System Test Sandbox — Release v${VERSION} -macOS 桌面自动化沙箱。通过 CLI 启动 Tauri 沙箱窗口,内置 xterm.js 终端运行命令行工具(如 Claude Code),支持截图和输入模拟。 +macOS 桌面自动化沙箱。通过 CLI 启动 Electron 沙箱窗口,内置 xterm.js 终端运行命令行工具(如 Claude Code),支持截图和输入模拟。 ## 文件说明 ``` release/ ├── sandbox # CLI 工具(命令行入口) -├── System Test Sandbox.app/ # Tauri 沙箱 macOS 应用 +├── sandbox-daemon # 守护进程(CLI 自动管理) +├── System Test Sandbox.app/ # Electron 沙箱 macOS 应用 └── README.md # 本文件 ``` @@ -54,45 +55,43 @@ release/ ### 截图 \`\`\`bash -# 自动发现沙箱窗口并截图(保存为 PNG) -./sandbox screenshot -o screenshot.png - -# 指定窗口 ID 截图 -./sandbox screenshot --window-id 12345 -o screenshot.png +# 截取指定沙箱窗口 +./sandbox screenshot --id -o screenshot.png \`\`\` ### 其他命令 \`\`\`bash -# 列出所有可见窗口 -./sandbox windows +# 列出所有沙箱 +./sandbox list + +# 查看沙箱详情 +./sandbox inspect # 关闭沙箱 -./sandbox shutdown +./sandbox close \`\`\` ### 示例工作流 \`\`\`bash -# 1. 启动 Claude Code +# 1. 启动 Claude Code(自动打开 Electron 窗口) ./sandbox start claude # 2. 等待 Claude 启动(约 10 秒) sleep 10 # 3. 截图查看状态 -./sandbox screenshot -o screenshot.png +./sandbox screenshot --id -o screenshot.png -# 4. 关闭沙箱 -./sandbox shutdown -\`\`\` +# 4. 启动另一个沙箱(自动创建新 Tab) +./sandbox start zsh -\`\`\`bash -# 非交互式快速提问 -./sandbox start claude -- -p "用 Python 写一个 hello world" -sleep 30 -./sandbox screenshot -o claude_response.png -./sandbox shutdown +# 5. 列出所有沙箱 +./sandbox list + +# 6. 关闭指定沙箱 +./sandbox close \`\`\` ## 三、架构 @@ -102,21 +101,24 @@ sandbox start claude │ ▼ CLI (sandbox) - │ spawn System Test Sandbox.app --mode=cli --cmd=claude + │ 1. 启动 sandbox-daemon(如未运行) + │ 2. 通过 HTTP 创建沙箱 + │ 3. 启动 Electron 窗口(如未运行) ▼ -Tauri 沙箱窗口 - ┌────────────────────────────────────────────┐ - │ 终端面板 (xterm.js) │ Screenshot Preview │ - │ ← Claude 运行在这里 │ │ - ├────────────────────────────────────────────┤ - │ Control Panel: Screenshot / Spawn / Click │ - ├────────────────────────────────────────────┤ - │ Status: Server :5801 | Processes: X | ... │ - └────────────────────────────────────────────┘ - │ HTTP :5801 +sandbox-daemon (HTTP :15801) + - 管理 PTY 进程 + - 提供截图/输入 API + - WebSocket PTY 终端 + │ ▼ - 内嵌 HTTP API (axum) - - /screenshot, /input/click, /pty/write, ... +Electron 窗口 (Chromium) + ┌────────────────────────────────────┐ + │ Tab: claude Tab: zsh Tab: ... │ + ├────────────────────────────────────┤ + │ xterm.js 终端 │ + │ ← PTY WebSocket 连接 │ + │ 标准 term.write() 渲染 │ + └────────────────────────────────────┘ \`\`\` ## 四、常见问题 @@ -131,8 +133,8 @@ A: 检查「辅助功能」权限是否已授予。 A: 确保 \`System Test Sandbox.app\` 与 \`sandbox\` 在同一目录下。 **Q: 沙箱窗口内终端空白?** -A: 等待几秒让 Claude 启动,终端会自动连接 PTY 输出。 +A: 等待几秒让 CLI 工具启动,终端会自动连接 PTY 输出。 --- -**版本**: v${VERSION} | **构建时间**: 2026-05-30 03:34 +**版本**: v${VERSION} | **构建时间**: 2026-05-31 11:35 diff --git a/sandbox-web/index.html b/sandbox-web/index.html deleted file mode 100644 index 8520f58..0000000 --- a/sandbox-web/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - System Test Sandbox - - - -
- - - diff --git a/sandbox-web/package.json b/sandbox-web/package.json deleted file mode 100644 index 1f6843f..0000000 --- a/sandbox-web/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "sandbox-web", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "typecheck": "tsc --noEmit", - "test:unit": "vitest run", - "format:check": "prettier --check 'src/**/*.{ts,tsx}'", - "format": "prettier --write 'src/**/*.{ts,tsx}'" - }, - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-shell": "^2", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", - "@vitest/coverage-v8": "^2.1.9", - "autoprefixer": "^10.4.20", - "jsdom": "^29.1.1", - "postcss": "^8.4.49", - "prettier": "^3.4.2", - "tailwindcss": "^3.4.17", - "typescript": "^5.7.2", - "vite": "^6.0.5", - "vitest": "^2.1.8" - }, - "pnpm": { - "overrides": { - "glob": "^10.5.0" - } - } -} diff --git a/sandbox-web/pnpm-lock.yaml b/sandbox-web/pnpm-lock.yaml deleted file mode 100644 index e420e12..0000000 --- a/sandbox-web/pnpm-lock.yaml +++ /dev/null @@ -1,3207 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - glob: ^10.5.0 - -importers: - - .: - dependencies: - '@tauri-apps/api': - specifier: ^2 - version: 2.11.0 - '@tauri-apps/plugin-shell': - specifier: ^2 - version: 2.3.5 - '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 - '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 - react: - specifier: ^18.3.1 - version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) - devDependencies: - '@testing-library/jest-dom': - specifier: ^6.9.1 - version: 6.9.1 - '@testing-library/react': - specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/react': - specifier: ^18.3.1 - version: 18.3.28 - '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.7(@types/react@18.3.28) - '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.7.0(vite@6.4.2(jiti@1.21.7)) - '@vitest/coverage-v8': - specifier: ^2.1.9 - version: 2.1.9(vitest@2.1.9(jsdom@29.1.1)) - autoprefixer: - specifier: ^10.4.20 - version: 10.5.0(postcss@8.5.14) - jsdom: - specifier: ^29.1.1 - version: 29.1.1 - postcss: - specifier: ^8.4.49 - version: 8.5.14 - prettier: - specifier: ^3.4.2 - version: 3.8.3 - tailwindcss: - specifier: ^3.4.17 - version: 3.4.19 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - vite: - specifier: ^6.0.5 - version: 6.4.2(jiti@1.21.7) - vitest: - specifier: ^2.1.8 - version: 2.1.9(jsdom@29.1.1) - -packages: - - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@asamuzakjp/css-color@5.1.11': - resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/dom-selector@7.1.1': - resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/generational-cache@1.0.1': - resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/nwsapi@2.3.9': - resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.3': - resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.29.2': - resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@bramus/specificity@2.4.2': - resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} - hasBin: true - - '@csstools/color-helpers@6.0.2': - resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} - engines: {node: '>=20.19.0'} - - '@csstools/css-calc@3.2.1': - resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-color-parser@4.1.1': - resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-parser-algorithms@4.0.0': - resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-syntax-patches-for-csstree@1.1.4': - resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} - peerDependencies: - css-tree: ^3.2.1 - peerDependenciesMeta: - css-tree: - optional: true - - '@csstools/css-tokenizer@4.0.0': - resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} - engines: {node: '>=20.19.0'} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@exodus/bytes@1.15.1': - resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - peerDependencies: - '@noble/hashes': ^1.8.0 || ^2.0.0 - peerDependenciesMeta: - '@noble/hashes': - optional: true - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} - engines: {node: '>=8'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} - cpu: [x64] - os: [win32] - - '@tauri-apps/api@2.11.0': - resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} - - '@tauri-apps/plugin-shell@2.3.5': - resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==} - - '@testing-library/dom@10.4.1': - resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} - engines: {node: '>=18'} - - '@testing-library/jest-dom@6.9.1': - resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - - '@testing-library/react@16.3.2': - resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} - engines: {node: '>=18'} - peerDependencies: - '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 || ^19.0.0 - '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} - peerDependencies: - '@types/react': ^18.0.0 - - '@types/react@18.3.28': - resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} - - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/coverage-v8@2.1.9': - resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} - peerDependencies: - '@vitest/browser': 2.1.9 - vitest: 2.1.9 - peerDependenciesMeta: - '@vitest/browser': - optional: true - - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} - - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} - - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} - - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - - '@xterm/addon-fit@0.11.0': - resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} - - '@xterm/xterm@6.0.0': - resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - baseline-browser-mapping@2.10.29: - resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - bidi-js@1.0.3: - resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - caniuse-lite@1.0.30001792: - resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} - - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} - - check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css-tree@3.2.1: - resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - - css.escape@1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - data-urls@7.0.0: - resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-accessibility-api@0.6.3: - resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - electron-to-chromium@1.5.356: - resolution: {integrity: sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - entities@8.0.0: - resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} - engines: {node: '>=20.19.0'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} - engines: {node: '>= 0.4'} - - html-encoding-sniffer@6.0.0: - resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsdom@29.1.1: - resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@11.5.0: - resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==} - engines: {node: 20 || >=22} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - mdn-data@2.27.1: - resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-releases@2.0.44: - resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parse5@8.0.1: - resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.1.0: - resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} - engines: {node: '>=14'} - hasBin: true - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tailwindcss@3.4.19: - resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} - engines: {node: '>=14.0.0'} - hasBin: true - - test-exclude@7.0.2: - resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} - engines: {node: '>=18'} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - tldts-core@7.0.30: - resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} - - tldts@7.0.30: - resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} - hasBin: true - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tough-cookie@6.0.1: - resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} - engines: {node: '>=16'} - - tr46@6.0.0: - resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} - engines: {node: '>=20'} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} - engines: {node: '>=20.18.1'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vite@6.4.2: - resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - 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 - - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - webidl-conversions@8.0.1: - resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} - engines: {node: '>=20'} - - whatwg-mimetype@5.0.0: - resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} - engines: {node: '>=20'} - - whatwg-url@16.0.1: - resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - -snapshots: - - '@adobe/css-tools@4.4.4': {} - - '@alloc/quick-lru@5.2.0': {} - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@asamuzakjp/css-color@5.1.11': - dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@asamuzakjp/dom-selector@7.1.1': - dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@asamuzakjp/nwsapi': 2.3.9 - bidi-js: 1.0.3 - css-tree: 3.2.1 - is-potential-custom-element-name: 1.0.1 - - '@asamuzakjp/generational-cache@1.0.1': {} - - '@asamuzakjp/nwsapi@2.3.9': {} - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.3': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.3 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/runtime@7.29.2': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@bcoe/v8-coverage@0.2.3': {} - - '@bramus/specificity@2.4.2': - dependencies: - css-tree: 3.2.1 - - '@csstools/color-helpers@6.0.2': {} - - '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': - optionalDependencies: - css-tree: 3.2.1 - - '@csstools/css-tokenizer@4.0.0': {} - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/aix-ppc64@0.25.12': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.25.12': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@esbuild/win32-x64@0.25.12': - optional: true - - '@exodus/bytes@1.15.1': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@istanbuljs/schema@0.1.6': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@rolldown/pluginutils@1.0.0-beta.27': {} - - '@rollup/rollup-android-arm-eabi@4.60.4': - optional: true - - '@rollup/rollup-android-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-x64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.4': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.4': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.4': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.4': - optional: true - - '@tauri-apps/api@2.11.0': {} - - '@tauri-apps/plugin-shell@2.3.5': - dependencies: - '@tauri-apps/api': 2.11.0 - - '@testing-library/dom@10.4.1': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.29.2 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - picocolors: 1.1.1 - pretty-format: 27.5.1 - - '@testing-library/jest-dom@6.9.1': - dependencies: - '@adobe/css-tools': 4.4.4 - aria-query: 5.3.2 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - picocolors: 1.1.1 - redent: 3.0.0 - - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@testing-library/dom': 10.4.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@types/aria-query@5.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/estree@1.0.8': {} - - '@types/estree@1.0.9': {} - - '@types/prop-types@15.7.15': {} - - '@types/react-dom@18.3.7(@types/react@18.3.28)': - dependencies: - '@types/react': 18.3.28 - - '@types/react@18.3.28': - dependencies: - '@types/prop-types': 15.7.15 - csstype: 3.2.3 - - '@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@1.21.7))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 6.4.2(jiti@1.21.7) - transitivePeerDependencies: - - supports-color - - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(jsdom@29.1.1))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.2 - tinyrainbow: 1.2.0 - vitest: 2.1.9(jsdom@29.1.1) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@2.1.9': - dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - tinyrainbow: 1.2.0 - - '@vitest/mocker@2.1.9(vite@5.4.21)': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 5.4.21 - - '@vitest/pretty-format@2.1.9': - dependencies: - tinyrainbow: 1.2.0 - - '@vitest/runner@2.1.9': - dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 - - '@vitest/snapshot@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - magic-string: 0.30.21 - pathe: 1.1.2 - - '@vitest/spy@2.1.9': - dependencies: - tinyspy: 3.0.2 - - '@vitest/utils@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.2.1 - tinyrainbow: 1.2.0 - - '@xterm/addon-fit@0.11.0': {} - - '@xterm/xterm@6.0.0': {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.3: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - arg@5.0.2: {} - - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - aria-query@5.3.2: {} - - assertion-error@2.0.1: {} - - autoprefixer@10.5.0(postcss@8.5.14): - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001792 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.14 - postcss-value-parser: 4.2.0 - - balanced-match@1.0.2: {} - - balanced-match@4.0.4: {} - - baseline-browser-mapping@2.10.29: {} - - bidi-js@1.0.3: - dependencies: - require-from-string: 2.0.2 - - binary-extensions@2.3.0: {} - - brace-expansion@2.1.0: - dependencies: - balanced-match: 1.0.2 - - brace-expansion@5.0.6: - dependencies: - balanced-match: 4.0.4 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.29 - caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.356 - node-releases: 2.0.44 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - cac@6.7.14: {} - - camelcase-css@2.0.1: {} - - caniuse-lite@1.0.30001792: {} - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 - - check-error@2.1.3: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - commander@4.1.1: {} - - convert-source-map@2.0.0: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css-tree@3.2.1: - dependencies: - mdn-data: 2.27.1 - source-map-js: 1.2.1 - - css.escape@1.5.1: {} - - cssesc@3.0.0: {} - - csstype@3.2.3: {} - - data-urls@7.0.0: - dependencies: - whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js@10.6.0: {} - - deep-eql@5.0.2: {} - - dequal@2.0.3: {} - - didyoumean@1.2.2: {} - - dlv@1.1.3: {} - - dom-accessibility-api@0.5.16: {} - - dom-accessibility-api@0.6.3: {} - - eastasianwidth@0.2.0: {} - - electron-to-chromium@1.5.356: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - entities@8.0.0: {} - - es-errors@1.3.0: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - - escalade@3.2.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - expect-type@1.3.0: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - fraction.js@5.3.4: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - has-flag@4.0.0: {} - - hasown@2.0.3: - dependencies: - function-bind: 1.1.2 - - html-encoding-sniffer@6.0.0: - dependencies: - '@exodus/bytes': 1.15.1 - transitivePeerDependencies: - - '@noble/hashes' - - html-escaper@2.0.2: {} - - indent-string@4.0.0: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.3 - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-potential-custom-element-name@1.0.1: {} - - isexe@2.0.0: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@1.21.7: {} - - js-tokens@4.0.0: {} - - jsdom@29.1.1: - dependencies: - '@asamuzakjp/css-color': 5.1.11 - '@asamuzakjp/dom-selector': 7.1.1 - '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) - '@exodus/bytes': 1.15.1 - css-tree: 3.2.1 - data-urls: 7.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0 - is-potential-custom-element-name: 1.0.1 - lru-cache: 11.5.0 - parse5: 8.0.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 6.0.1 - undici: 7.25.0 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 8.0.1 - whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - '@noble/hashes' - - jsesc@3.1.0: {} - - json5@2.2.3: {} - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - - lru-cache@11.5.0: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lz-string@1.5.0: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - magicast@0.3.5: - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - - make-dir@4.0.0: - dependencies: - semver: 7.8.0 - - mdn-data@2.27.1: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - - min-indent@1.0.1: {} - - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.6 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.1.0 - - minipass@7.1.3: {} - - ms@2.1.3: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.12: {} - - node-releases@2.0.44: {} - - normalize-path@3.0.0: {} - - object-assign@4.1.1: {} - - object-hash@3.0.0: {} - - package-json-from-dist@1.0.1: {} - - parse5@8.0.1: - dependencies: - entities: 8.0.0 - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - - pathe@1.1.2: {} - - pathval@2.0.1: {} - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.4: {} - - pify@2.3.0: {} - - pirates@4.0.7: {} - - postcss-import@15.1.0(postcss@8.5.14): - dependencies: - postcss: 8.5.14 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.12 - - postcss-js@4.1.0(postcss@8.5.14): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.5.14 - - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.14): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - jiti: 1.21.7 - postcss: 8.5.14 - - postcss-nested@6.2.0(postcss@8.5.14): - dependencies: - postcss: 8.5.14 - postcss-selector-parser: 6.1.2 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.14: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.8.3: {} - - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-is@17.0.2: {} - - react-refresh@0.17.0: {} - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - read-cache@1.0.0: - dependencies: - pify: 2.3.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - - require-from-string@2.0.2: {} - - resolve@1.22.12: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.1.0: {} - - rollup@4.60.4: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - semver@6.3.1: {} - - semver@7.8.0: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} - - source-map-js@1.2.1: {} - - stackback@0.0.2: {} - - std-env@3.10.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 - ts-interface-checker: 0.1.13 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - symbol-tree@3.2.4: {} - - tailwindcss@3.4.19: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.3 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.14 - postcss-import: 15.1.0(postcss@8.5.14) - postcss-js: 4.1.0(postcss@8.5.14) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.14) - postcss-nested: 6.2.0(postcss@8.5.14) - postcss-selector-parser: 6.1.2 - resolve: 1.22.12 - sucrase: 3.35.1 - transitivePeerDependencies: - - tsx - - yaml - - test-exclude@7.0.2: - dependencies: - '@istanbuljs/schema': 0.1.6 - glob: 10.5.0 - minimatch: 10.2.5 - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tinypool@1.1.1: {} - - tinyrainbow@1.2.0: {} - - tinyspy@3.0.2: {} - - tldts-core@7.0.30: {} - - tldts@7.0.30: - dependencies: - tldts-core: 7.0.30 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tough-cookie@6.0.1: - dependencies: - tldts: 7.0.30 - - tr46@6.0.0: - dependencies: - punycode: 2.3.1 - - ts-interface-checker@0.1.13: {} - - typescript@5.9.3: {} - - undici@7.25.0: {} - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - util-deprecate@1.0.2: {} - - vite-node@2.1.9: - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 5.4.21 - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite@5.4.21: - dependencies: - esbuild: 0.21.5 - postcss: 8.5.14 - rollup: 4.60.4 - optionalDependencies: - fsevents: 2.3.3 - - vite@6.4.2(jiti@1.21.7): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.14 - rollup: 4.60.4 - tinyglobby: 0.2.16 - optionalDependencies: - fsevents: 2.3.3 - jiti: 1.21.7 - - vitest@2.1.9(jsdom@29.1.1): - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 1.1.2 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 1.2.0 - vite: 5.4.21 - vite-node: 2.1.9 - why-is-node-running: 2.3.0 - optionalDependencies: - jsdom: 29.1.1 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - webidl-conversions@8.0.1: {} - - whatwg-mimetype@5.0.0: {} - - whatwg-url@16.0.1: - dependencies: - '@exodus/bytes': 1.15.1 - tr46: 6.0.0 - webidl-conversions: 8.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - - xml-name-validator@5.0.0: {} - - xmlchars@2.2.0: {} - - yallist@3.1.1: {} diff --git a/sandbox-web/postcss.config.js b/sandbox-web/postcss.config.js deleted file mode 100644 index 2aa7205..0000000 --- a/sandbox-web/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/sandbox-web/src/__tests__/App.test.tsx b/sandbox-web/src/__tests__/App.test.tsx deleted file mode 100644 index d189ecb..0000000 --- a/sandbox-web/src/__tests__/App.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("App", () => { - it("test runner works", () => { - expect(true).toBe(true); - }); - - it("vitest environment is node", () => { - // Verify the test environment matches vitest config - expect(typeof globalThis).toBe("object"); - }); -}); - -describe("TypeScript type system", () => { - it("supports optional chaining", () => { - const obj: { a?: { b?: number } } | null = { a: { b: 42 } }; - expect(obj?.a?.b).toBe(42); - expect(obj?.a?.b).toBeDefined(); - }); - - it("supports nullish coalescing", () => { - const value: string | null = null; - expect(value ?? "default").toBe("default"); - }); -}); diff --git a/sandbox-web/src/__tests__/api.test.ts b/sandbox-web/src/__tests__/api.test.ts deleted file mode 100644 index bdb219e..0000000 --- a/sandbox-web/src/__tests__/api.test.ts +++ /dev/null @@ -1,489 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; - -// Mock fetch globally -const mockFetch = vi.fn(); -vi.stubGlobal("fetch", mockFetch); - -// Helper to create a mock Response -function mockResponse( - ok: boolean, - status: number, - body: string | Blob, - contentType = "application/json", -) { - const resp = { - ok, - status, - headers: new Headers({ "content-type": contentType }), - json: () => Promise.resolve(JSON.parse(body as string)), - text: () => Promise.resolve(body as string), - blob: () => Promise.resolve(body instanceof Blob ? body : new Blob([body])), - clone() { - return resp; - }, - }; - return resp as unknown as Response; -} - -// Import after mocks are set up — api.ts uses fetch at module level via functions -import * as api from "../api"; - -describe("API client", () => { - beforeEach(() => { - mockFetch.mockReset(); - // Reset URL to no search params - window.history.replaceState({}, "", "/"); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - // ── Port resolution ─────────────────────────────────── - - describe("port resolution", () => { - it("uses default port 5801 when no query param", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"status":"ok","version":"0.2.0","uptime_secs":0,"sandbox_id":null}', - ), - ); - await api.health(); - expect(mockFetch).toHaveBeenCalledWith("http://127.0.0.1:5801/health"); - }); - - it("uses sandbox_port query parameter when present", async () => { - window.history.replaceState({}, "", "/?sandbox_port=9999"); - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"status":"ok","version":"0.2.0","uptime_secs":0,"sandbox_id":null}', - ), - ); - await api.health(); - expect(mockFetch).toHaveBeenCalledWith("http://127.0.0.1:9999/health"); - }); - }); - - // ── Health ──────────────────────────────────────────── - - describe("health()", () => { - it("returns HealthResponse on success", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"status":"ok","version":"0.2.0","uptime_secs":42,"sandbox_id":"abc123"}', - ), - ); - const result = await api.health(); - expect(result.status).toBe("ok"); - expect(result.version).toBe("0.2.0"); - expect(result.uptime_secs).toBe(42); - expect(result.sandbox_id).toBe("abc123"); - }); - }); - - // ── Sandbox Info ────────────────────────────────────── - - describe("sandboxInfo()", () => { - it("returns SandboxInfo on success", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"sandbox_id":"abc123","window_id":42,"uptime_secs":60}', - ), - ); - const result = await api.sandboxInfo(); - expect(result.sandbox_id).toBe("abc123"); - expect(result.window_id).toBe(42); - expect(result.uptime_secs).toBe(60); - }); - }); - - // ── Pending CLI ───────────────────────────────────── - - describe("getPendingCli()", () => { - it("returns pending CLI info on success", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"command":"claude","args":["--help"]}', - ), - ); - const result = await api.getPendingCli(); - expect(result.command).toBe("claude"); - expect(result.args).toEqual(["--help"]); - expect(mockFetch).toHaveBeenCalledWith( - "http://127.0.0.1:5801/sandbox/pending-cli", - ); - }); - - it("returns { command: null } on non-ok response", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 500, "not found"), - ); - const result = await api.getPendingCli(); - expect(result).toEqual({ command: null }); - }); - - it("returns pending CLI with no args", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"command":"zsh"}'), - ); - const result = await api.getPendingCli(); - expect(result.command).toBe("zsh"); - expect(result.args).toBeUndefined(); - }); - }); - - // ── request() helper error handling ────────────────── - - describe("request() error handling", () => { - it("throws on non-ok response with JSON error", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 400, '{"error":"bad request"}'), - ); - await expect(api.click(0, 0)).rejects.toThrow("HTTP 400: bad request"); - }); - - it("throws on non-ok response with plain text body", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 500, "internal server error"), - ); - await expect(api.click(0, 0)).rejects.toThrow( - "HTTP 500: internal server error", - ); - }); - - it("throws on non-ok response with invalid JSON body", async () => { - mockFetch.mockResolvedValueOnce(mockResponse(false, 502, "{invalid")); - await expect(api.click(0, 0)).rejects.toThrow("HTTP 502"); - }); - }); - - // ── Screenshot ──────────────────────────────────────── - - describe("takeScreenshot()", () => { - it("returns blob URL on success", async () => { - const blob = new Blob(["fake-png-data"], { type: "image/png" }); - mockFetch.mockResolvedValueOnce(mockResponse(true, 200, blob)); - const result = await api.takeScreenshot(); - expect(result).toMatch(/^blob:/); - }); - - it("throws on failure", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 500, "capture failed"), - ); - await expect(api.takeScreenshot()).rejects.toThrow("Screenshot failed"); - }); - - it("throws when sandbox window is not available (400)", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 400, "Sandbox window not available"), - ); - await expect(api.takeScreenshot()).rejects.toThrow("Screenshot failed"); - }); - }); - - describe("takeScreenshotRegion()", () => { - it("returns blob URL on success", async () => { - const blob = new Blob(["fake-png-data"], { type: "image/png" }); - mockFetch.mockResolvedValueOnce(mockResponse(true, 200, blob)); - const result = await api.takeScreenshotRegion(10, 20, 100, 200); - expect(result).toMatch(/^blob:/); - expect(mockFetch).toHaveBeenCalledWith( - "http://127.0.0.1:5801/screenshot/region?x=10&y=20&width=100&height=200", - ); - }); - - it("throws on failure", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(false, 500, "region failed"), - ); - await expect(api.takeScreenshotRegion(0, 0, 1, 1)).rejects.toThrow( - "Screenshot region failed", - ); - }); - }); - - // ── Input methods ───────────────────────────────────── - - describe("click()", () => { - it("sends POST with click data", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"clicked":{"x":100,"y":200,"button":"left"}}', - ), - ); - await api.click(100, 200, "left"); - const call = mockFetch.mock.calls[0]; - expect(call[1]?.method).toBe("POST"); - expect(JSON.parse(call[1]?.body as string)).toEqual({ - x: 100, - y: 200, - button: "left", - }); - }); - - it("defaults to left button", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"clicked":{"x":0,"y":0,"button":"left"}}'), - ); - await api.click(0, 0); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body.button).toBe("left"); - }); - }); - - describe("typeText()", () => { - it("sends POST with text", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"typed":"hello"}'), - ); - await api.typeText("hello"); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body.text).toBe("hello"); - }); - }); - - describe("pressKey()", () => { - it("sends POST with key and modifiers", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse( - true, - 200, - '{"pressed":{"key":"return","modifiers":["cmd"]}}', - ), - ); - await api.pressKey("return", ["cmd"]); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body.key).toBe("return"); - expect(body.modifiers).toEqual(["cmd"]); - }); - - it("defaults to empty modifiers", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"pressed":{"key":"escape","modifiers":[]}}'), - ); - await api.pressKey("escape"); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body.modifiers).toEqual([]); - }); - }); - - describe("scroll()", () => { - it("sends POST with scroll data", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"scrolled":true}'), - ); - await api.scroll(50, 50, "down", 5); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body).toEqual({ x: 50, y: 50, direction: "down", amount: 5 }); - }); - }); - - describe("drag()", () => { - it("sends POST with drag coordinates", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"dragged":true}'), - ); - await api.drag(0, 0, 100, 200); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body).toEqual({ - from_x: 0, - from_y: 0, - to_x: 100, - to_y: 200, - }); - }); - }); - - // ── Process management ──────────────────────────────── - - describe("spawnApp()", () => { - it("returns ProcessInfo on success", async () => { - const info = { - pid: 1001, - name: "Safari", - path: "/Apps/Safari.app", - is_running: true, - }; - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, JSON.stringify(info)), - ); - const result = await api.spawnApp("/Apps/Safari.app"); - expect(result.pid).toBe(1001); - expect(result.name).toBe("Safari"); - }); - - it("throws on failure", async () => { - mockFetch.mockResolvedValueOnce(mockResponse(false, 500, "not found")); - await expect(api.spawnApp("/bad")).rejects.toThrow("spawnApp failed"); - }); - }); - - describe("spawnCli()", () => { - it("returns ProcessInfo on success", async () => { - const info = { pid: 1002, name: "echo", path: null, is_running: true }; - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, JSON.stringify(info)), - ); - const result = await api.spawnCli("echo", ["hello"]); - expect(result.pid).toBe(1002); - const call = mockFetch.mock.calls[0]; - expect(call[1]?.method).toBe("POST"); - const body = JSON.parse(call[1]?.body as string); - expect(body.command).toBe("echo"); - expect(body.args).toEqual(["hello"]); - }); - - it("throws on failure", async () => { - mockFetch.mockResolvedValueOnce(mockResponse(false, 500, "failed")); - await expect(api.spawnCli("bad", [])).rejects.toThrow("spawnCli failed"); - }); - }); - - describe("listProcesses()", () => { - it("returns ProcessInfo array", async () => { - const procs = [ - { pid: 1, name: "a", path: null, is_running: true }, - { pid: 2, name: "b", path: null, is_running: false }, - ]; - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, JSON.stringify(procs)), - ); - const result = await api.listProcesses(); - expect(result).toHaveLength(2); - expect(result[0].pid).toBe(1); - }); - }); - - describe("killProcess()", () => { - it("sends POST with pid", async () => { - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, '{"killed":123}'), - ); - await api.killProcess(123); - const body = JSON.parse(mockFetch.mock.calls[0][1]?.body as string); - expect(body.pid).toBe(123); - }); - }); - - // ── PTY WebSocket ───────────────────────────────────── - - describe("ptyConnectWs()", () => { - it("returns connection object with expected methods", () => { - // Mock WebSocket globally (without affecting fetch) - const mockWs = { - readyState: 1, // OPEN - send: vi.fn(), - close: vi.fn(), - onmessage: null as ((e: { data: string }) => void) | null, - }; - vi.stubGlobal( - "WebSocket", - vi.fn().mockImplementation(() => mockWs), - ); - - const conn = api.ptyConnectWs(42); - expect(conn).toHaveProperty("ws"); - expect(typeof conn.sendInput).toBe("function"); - expect(typeof conn.resize).toBe("function"); - expect(typeof conn.onOutput).toBe("function"); - expect(typeof conn.onError).toBe("function"); - expect(typeof conn.onClose).toBe("function"); - expect(typeof conn.close).toBe("function"); - - vi.unstubAllGlobals(); - // Restore fetch mock that unstubAllGlobals removed - vi.stubGlobal("fetch", mockFetch); - }); - - it("fires onError callback on WebSocket error", () => { - const mockWs = { - readyState: 1, - send: vi.fn(), - close: vi.fn(), - onopen: null as (() => void) | null, - onclose: null as ((e: { code: number; reason: string }) => void) | null, - onerror: null as ((e: Event) => void) | null, - onmessage: null as ((e: { data: string }) => void) | null, - }; - vi.stubGlobal( - "WebSocket", - vi.fn().mockImplementation(() => mockWs), - ); - - const conn = api.ptyConnectWs(42); - const errorCb = vi.fn(); - conn.onError(errorCb); - - // Simulate WebSocket error - mockWs.onerror?.({} as Event); - - expect(errorCb).toHaveBeenCalledOnce(); - expect(errorCb.mock.calls[0][0]).toMatch(/WebSocket connection to PTY 42 failed/); - - vi.unstubAllGlobals(); - vi.stubGlobal("fetch", mockFetch); - }); - - it("fires onClose callback on WebSocket close", () => { - const mockWs = { - readyState: 1, - send: vi.fn(), - close: vi.fn(), - onopen: null as (() => void) | null, - onclose: null as ((e: { code: number; reason: string }) => void) | null, - onerror: null as ((e: Event) => void) | null, - onmessage: null as ((e: { data: string }) => void) | null, - }; - vi.stubGlobal( - "WebSocket", - vi.fn().mockImplementation(() => mockWs), - ); - - const conn = api.ptyConnectWs(42); - const closeCb = vi.fn(); - conn.onClose(closeCb); - - // Simulate WebSocket close - mockWs.onclose?.({ code: 1006, reason: "abnormal" }); - - expect(closeCb).toHaveBeenCalledOnce(); - expect(closeCb.mock.calls[0]).toEqual([1006, "abnormal"]); - - vi.unstubAllGlobals(); - vi.stubGlobal("fetch", mockFetch); - }); - }); - - // ── Windows ─────────────────────────────────────────── - - describe("listWindows()", () => { - it("returns window array", async () => { - const windows = [ - [42, "Terminal"], - [43, "Safari"], - ]; - mockFetch.mockResolvedValueOnce( - mockResponse(true, 200, JSON.stringify(windows)), - ); - const result = await api.listWindows(); - expect(result).toHaveLength(2); - expect(result[0]).toEqual([42, "Terminal"]); - }); - }); -}); diff --git a/sandbox-web/src/__tests__/components.test.tsx b/sandbox-web/src/__tests__/components.test.tsx deleted file mode 100644 index 20ce4d2..0000000 --- a/sandbox-web/src/__tests__/components.test.tsx +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, screen, fireEvent, cleanup } from "@testing-library/react"; -import Dashboard from "../components/Dashboard"; -import Sidebar from "../components/Sidebar"; -import { ThemeProvider } from "../themes/ThemeContext"; - -// ── Dashboard ────────────────────────────────────────────────── - -describe("Dashboard", () => { - const defaultProps = { - command: "claude", - connected: true, - activePid: null as number | null, - onSpawnReady: vi.fn(), - onScreenshot: vi.fn(), - }; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - cleanup(); - }); - - it("renders command name in the card header", () => { - render( - - - , - ); - expect(screen.getByText(/claude \(Sandboxed\)/)).toBeDefined(); - }); - - it("renders Dashboard title in header", () => { - render( - - - , - ); - expect(screen.getByText("Dashboard")).toBeDefined(); - }); - - it("renders Screenshot button", () => { - render( - - - , - ); - const btn = screen.getByTitle("Take Screenshot"); - expect(btn).toBeDefined(); - }); - - it("calls onScreenshot when Screenshot button is clicked", () => { - render( - - - , - ); - fireEvent.click(screen.getByTitle("Take Screenshot")); - expect(defaultProps.onScreenshot).toHaveBeenCalledOnce(); - }); - - it("renders 'Create New Sandbox' button", () => { - render( - - - , - ); - expect(screen.getByText("Create New Sandbox")).toBeDefined(); - }); - - it("shows -- stats when not connected", () => { - render( - - - , - ); - const dashes = screen.getAllByText("--"); - expect(dashes.length).toBeGreaterThanOrEqual(3); - }); - - it("shows percentage stats when connected", () => { - render( - - - , - ); - expect(screen.getByText("12%")).toBeDefined(); - expect(screen.getByText("180MB")).toBeDefined(); - }); - - it("renders stat labels", () => { - render( - - - , - ); - expect(screen.getByText("CPU")).toBeDefined(); - expect(screen.getByText("Memory")).toBeDefined(); - expect(screen.getByText("Network")).toBeDefined(); - }); - - it("renders children when provided", () => { - render( - - -
Test Child
-
-
, - ); - expect(screen.getByTestId("child").textContent).toBe("Test Child"); - }); - - it("renders error overlay when error prop is set", () => { - render( - - - , - ); - expect(screen.getByText("Something went wrong")).toBeDefined(); - }); - - it("does not render error overlay when error is null", () => { - render( - - - , - ); - // No warning triangle SVG with error text should be present - expect(screen.queryByText(/went wrong/)).toBeNull(); - }); -}); - -// ── Sidebar ──────────────────────────────────────────────────── - -describe("Sidebar", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - cleanup(); - }); - - it("renders the command name", () => { - render( - - - , - ); - expect(screen.getByText("claude")).toBeDefined(); - }); - - it("renders 'Sandbox' title", () => { - render( - - - , - ); - expect(screen.getByText("Sandbox")).toBeDefined(); - }); - - it("renders 'Instances' section", () => { - render( - - - , - ); - expect(screen.getByText("Instances")).toBeDefined(); - }); - - it("renders theme toggle button in dark mode", () => { - render( - - - , - ); - // Default is dark mode, so it should show "Light Mode" - expect(screen.getByText("Light Mode")).toBeDefined(); - }); - - it("toggle button has correct title", () => { - render( - - - , - ); - const btn = screen.getByTitle("Switch to light theme"); - expect(btn).toBeDefined(); - }); - - it("toggles to light mode on click", () => { - render( - - - , - ); - fireEvent.click(screen.getByTitle("Switch to light theme")); - expect(screen.getByText("Dark Mode")).toBeDefined(); - expect(screen.getByTitle("Switch to dark theme")).toBeDefined(); - }); -}); diff --git a/sandbox-web/src/__tests__/setup.ts b/sandbox-web/src/__tests__/setup.ts deleted file mode 100644 index 018a317..0000000 --- a/sandbox-web/src/__tests__/setup.ts +++ /dev/null @@ -1,56 +0,0 @@ -import "@testing-library/jest-dom/vitest"; - -// Node.js 25 ships a partial localStorage without clear(). -// Provide a complete implementation for test environments. -const store = new Map(); -const fullLocalStorage = { - getItem(key: string) { - return store.get(key) ?? null; - }, - setItem(key: string, value: string) { - store.set(key, String(value)); - }, - removeItem(key: string) { - store.delete(key); - }, - clear() { - store.clear(); - }, - get length() { - return store.size; - }, - key(index: number) { - const keys = [...store.keys()]; - return keys[index] ?? null; - }, -}; -Object.defineProperty(globalThis, "localStorage", { - value: fullLocalStorage, - writable: true, - configurable: true, -}); - -// xterm.js requires window.matchMedia -Object.defineProperty(globalThis, "matchMedia", { - value: (query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: () => {}, - removeListener: () => {}, - addEventListener: () => {}, - removeEventListener: () => {}, - dispatchEvent: () => false, - }), - writable: true, - configurable: true, -}); - -// api.ts uses URL.createObjectURL for screenshot blob URLs -if (!globalThis.URL.createObjectURL) { - globalThis.URL.createObjectURL = (obj: Blob) => - `blob:test-${Date.now()}-${obj.size}`; -} -if (!globalThis.URL.revokeObjectURL) { - globalThis.URL.revokeObjectURL = () => {}; -} diff --git a/sandbox-web/src/__tests__/terminal.test.tsx b/sandbox-web/src/__tests__/terminal.test.tsx deleted file mode 100644 index e296b80..0000000 --- a/sandbox-web/src/__tests__/terminal.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, cleanup } from "@testing-library/react"; -import { ThemeProvider } from "../themes/ThemeContext"; - -// We test Terminal indirectly through its exported behavior. -// The buildTerminalTheme function is internal, so we test via component rendering. -describe("Terminal component", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - cleanup(); - }); - - it("renders without crashing", async () => { - const { default: SandboxTerminal } = await import("../components/Terminal"); - const container = render( - - - , - ); - expect(container.container.querySelector(".xterm")).toBeTruthy(); - }); - - it("renders with activePid", async () => { - const { default: SandboxTerminal } = await import("../components/Terminal"); - const container = render( - - - , - ); - expect(container.container.querySelector(".xterm")).toBeTruthy(); - }); - - it("renders with null activePid", async () => { - const { default: SandboxTerminal } = await import("../components/Terminal"); - const container = render( - - - , - ); - expect(container.container.querySelector(".xterm")).toBeTruthy(); - }); - - it("creates an xterm instance", async () => { - const { default: SandboxTerminal } = await import("../components/Terminal"); - render( - - - , - ); - // xterm creates a .xterm element - const xtermEl = document.querySelector(".xterm"); - expect(xtermEl).not.toBeNull(); - }); -}); - -// Test the theme mapping logic indirectly -describe("Terminal theme mapping", () => { - it("theme object has required terminal keys", async () => { - const { default: SandboxTerminal } = await import("../components/Terminal"); - await import("../themes/ThemeContext"); - - // Verify theme structure by rendering and checking xterm is created - const container = render( - - - , - ); - const xtermEl = container.container.querySelector(".xterm"); - expect(xtermEl).toBeTruthy(); - cleanup(); - }); -}); diff --git a/sandbox-web/src/__tests__/theme-context.test.tsx b/sandbox-web/src/__tests__/theme-context.test.tsx deleted file mode 100644 index 6de493a..0000000 --- a/sandbox-web/src/__tests__/theme-context.test.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, screen, fireEvent, cleanup } from "@testing-library/react"; -import { - ThemeProvider, - useTheme, - ThemeContext, - STORAGE_KEY, -} from "../themes/ThemeContext"; - -function Consumer() { - const { theme, setTheme, toggleTheme, themes } = useTheme(); - return ( -
- {theme.id} - {theme.kind} - {themes.length} -
- ); -} - -describe("ThemeContext", () => { - beforeEach(() => { - localStorage.clear(); - document.documentElement.removeAttribute("data-theme"); - document.documentElement.removeAttribute("data-theme-kind"); - }); - - afterEach(() => { - cleanup(); - }); - - it("provides default theme (tokyo-night)", () => { - render( - - - , - ); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - expect(screen.getByTestId("theme-kind").textContent).toBe("dark"); - }); - - it("restores theme from localStorage", () => { - localStorage.setItem(STORAGE_KEY, "vscode-light"); - render( - - - , - ); - expect(screen.getByTestId("theme-id").textContent).toBe("vscode-light"); - expect(screen.getByTestId("theme-kind").textContent).toBe("light"); - }); - - it("falls back to default if stored id is invalid", () => { - localStorage.setItem(STORAGE_KEY, "nonexistent"); - render( - - - , - ); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - }); - - it("setTheme changes the active theme", () => { - render( - - - , - ); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - fireEvent.click(screen.getByTestId("set-light")); - expect(screen.getByTestId("theme-id").textContent).toBe("vscode-light"); - }); - - it("setTheme persists to localStorage", () => { - render( - - - , - ); - fireEvent.click(screen.getByTestId("set-light")); - expect(localStorage.getItem(STORAGE_KEY)).toBe("vscode-light"); - }); - - it("setTheme with invalid id is a no-op", () => { - render( - - - , - ); - fireEvent.click(screen.getByTestId("set-invalid")); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - }); - - it("toggleTheme cycles through themes", () => { - render( - - - , - ); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - fireEvent.click(screen.getByTestId("toggle")); - expect(screen.getByTestId("theme-id").textContent).toBe("vscode-light"); - fireEvent.click(screen.getByTestId("toggle")); - expect(screen.getByTestId("theme-id").textContent).toBe("tokyo-night"); - }); - - it("exposes list of all themes", () => { - render( - - - , - ); - expect(screen.getByTestId("theme-count").textContent).toBe("2"); - }); - - it("applies CSS custom properties on mount", () => { - render( - - - , - ); - const root = document.documentElement; - expect(root.getAttribute("data-theme")).toBe("tokyo-night"); - expect(root.getAttribute("data-theme-kind")).toBe("dark"); - // Check a few CSS variables are set - expect(root.style.getPropertyValue("--sandbox-bg-primary")).toBeTruthy(); - expect(root.style.getPropertyValue("--sandbox-fg-primary")).toBeTruthy(); - }); - - it("updates CSS custom properties on theme change", () => { - render( - - - , - ); - const root = document.documentElement; - const darkBg = root.style.getPropertyValue("--sandbox-bg-primary"); - fireEvent.click(screen.getByTestId("set-light")); - const lightBg = root.style.getPropertyValue("--sandbox-bg-primary"); - // Light and dark themes should have different backgrounds - expect(darkBg).not.toBe(lightBg); - expect(root.getAttribute("data-theme")).toBe("vscode-light"); - expect(root.getAttribute("data-theme-kind")).toBe("light"); - }); - - it("useTheme throws when used outside ThemeProvider", () => { - // Suppress console.error from React - const spy = vi.spyOn(console, "error").mockImplementation(() => {}); - function BadConsumer() { - useTheme(); - return null; - } - expect(() => render()).toThrow( - "useTheme must be used within ThemeProvider", - ); - spy.mockRestore(); - }); - - it("ThemeContext exports correct STORAGE_KEY", () => { - expect(STORAGE_KEY).toBe("sandbox-theme"); - }); - - it("ThemeContext default value is null (no provider)", () => { - // @ts-expect-error Accessing React internal for test - expect(ThemeContext._currentValue).toBeNull(); - }); -}); diff --git a/sandbox-web/src/__tests__/themes.test.ts b/sandbox-web/src/__tests__/themes.test.ts deleted file mode 100644 index 66afcf0..0000000 --- a/sandbox-web/src/__tests__/themes.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { themeRegistry } from "../themes/registry"; -import type { SandboxTheme } from "../themes/types"; - -describe("ThemeRegistry", () => { - it("has at least two themes registered", () => { - const themes = themeRegistry.list(); - expect(themes.length).toBeGreaterThanOrEqual(2); - }); - - it("default theme is tokyo-night (dark)", () => { - const t = themeRegistry.defaultTheme(); - expect(t.id).toBe("tokyo-night"); - expect(t.kind).toBe("dark"); - expect(t.name).toBe("Tokyo Night"); - }); - - it("has a light theme available", () => { - const themes = themeRegistry.list(); - const light = themes.find((t) => t.kind === "light"); - expect(light).toBeDefined(); - expect(light!.id).toBe("vscode-light"); - }); - - it("get returns undefined for unknown id", () => { - expect(themeRegistry.get("nonexistent")).toBeUndefined(); - }); - - it("get returns correct theme for valid id", () => { - const t = themeRegistry.get("tokyo-night"); - expect(t).toBeDefined(); - expect(t!.id).toBe("tokyo-night"); - }); - - it("list returns unique themes (no duplicates)", () => { - const themes = themeRegistry.list(); - const ids = themes.map((t) => t.id); - expect(new Set(ids).size).toBe(ids.length); - }); - - it("both themes have kind set", () => { - const themes = themeRegistry.list(); - for (const t of themes) { - expect(t.kind).toMatch(/^(dark|light)$/); - } - }); -}); - -describe("Theme color contract", () => { - const requiredColorKeys: (keyof SandboxTheme["colors"])[] = [ - "bgPrimary", - "bgSecondary", - "bgTertiary", - "fgPrimary", - "fgSecondary", - "fgTertiary", - "border", - "accent", - "scrollbarBg", - "scrollbarFg", - "success", - "error", - "titlebarBg", - "titlebarFg", - "sidebarBg", - "sidebarFg", - "sidebarBorder", - "sidebarActive", - "panelBg", - ]; - - const requiredTerminalKeys: (keyof SandboxTheme["terminal"])[] = [ - "background", - "foreground", - "cursor", - "cursorAccent", - "black", - "red", - "green", - "yellow", - "blue", - "magenta", - "cyan", - "white", - ]; - - themeRegistry.list().forEach((theme) => { - describe(theme.name, () => { - it("has all required color tokens as non-empty strings", () => { - for (const key of requiredColorKeys) { - expect(theme.colors[key]).toBeTypeOf("string"); - expect(theme.colors[key].length).toBeGreaterThan(0); - } - }); - - it("has all required terminal theme keys as non-empty strings", () => { - for (const key of requiredTerminalKeys) { - expect(theme.terminal[key]).toBeTypeOf("string"); - expect(theme.terminal[key].length).toBeGreaterThan(0); - } - }); - - it("has a valid id matching theme registration", () => { - const registered = themeRegistry.get(theme.id); - expect(registered).toBeDefined(); - expect(registered!.name).toBe(theme.name); - }); - - it("light theme is vscode-light, dark is tokyo-night", () => { - if (theme.kind === "dark") { - expect(theme.id).toBe("tokyo-night"); - } else { - expect(theme.id).toBe("vscode-light"); - } - }); - }); - }); -}); diff --git a/sandbox-web/src/api.ts b/sandbox-web/src/api.ts deleted file mode 100644 index 1116043..0000000 --- a/sandbox-web/src/api.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { debugLog, debugError } from "./logger"; - -/** - * Sandbox HTTP API client. - * - * Port resolution order: - * 1. `?sandbox_port=` in the page URL - * 2. `SANDOX_PORT` env var (Vite-injected at build time) - * 3. Default `5801` - */ - -function getPort(): number { - if (typeof window !== "undefined") { - const params = new URLSearchParams(window.location.search); - const p = params.get("sandbox_port"); - if (p) return Number(p); - } - return 5801; -} - -const BASE = () => `http://127.0.0.1:${getPort()}`; - -// ── Types ────────────────────────────────────────────── - -export interface ProcessInfo { - pid: number; - name: string; - path: string | null; - is_running: boolean; -} - -export interface HealthResponse { - status: string; - version: string; - uptime_secs: number; - sandbox_id: string | null; -} - -export interface SandboxInfo { - sandbox_id: string | null; - window_id: number | null; - uptime_secs: number; -} - -// ── Generic fetch helper ─────────────────────────────── - -async function request(path: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE()}${path}`, { - ...options, - headers: { "Content-Type": "application/json", ...options?.headers }, - }); - if (!res.ok) { - const body = await res.text(); - let msg = body; - try { - msg = JSON.parse(body).error ?? body; - } catch { - /* keep raw text */ - } - throw new Error(`HTTP ${res.status}: ${msg}`); - } - // Some endpoints return binary (image/png), caller handles raw response - return res as unknown as T; -} - -// ── Health & Info ────────────────────────────────────── - -export async function health(): Promise { - const res = await fetch(`${BASE()}/health`); - return res.json(); -} - -export async function sandboxInfo(): Promise { - const res = await fetch(`${BASE()}/sandbox/info`); - return res.json(); -} - -// ── Pending CLI ────────────────────────────────────── - -export interface PendingCli { - command: string | null; - args?: string[]; -} - -export async function getPendingCli(): Promise { - const res = await fetch(`${BASE()}/sandbox/pending-cli`); - if (!res.ok) return { command: null }; - return res.json(); -} - -// ── Screenshot ───────────────────────────────────────── - -/** Capture the sandbox window. Returns a Blob URL. */ -export async function takeScreenshot(): Promise { - const res = await fetch(`${BASE()}/screenshot`); - if (!res.ok) { - const body = await res.text(); - throw new Error(`Screenshot failed: ${body}`); - } - const blob = await res.blob(); - return URL.createObjectURL(blob); -} - -/** Capture a screen region. Returns a Blob URL. */ -export async function takeScreenshotRegion( - x: number, - y: number, - width: number, - height: number, -): Promise { - const res = await fetch( - `${BASE()}/screenshot/region?x=${x}&y=${y}&width=${width}&height=${height}`, - ); - if (!res.ok) { - const body = await res.text(); - throw new Error(`Screenshot region failed: ${body}`); - } - const blob = await res.blob(); - return URL.createObjectURL(blob); -} - -// ── Input ────────────────────────────────────────────── - -export async function click( - x: number, - y: number, - button: "left" | "right" | "middle" = "left", -): Promise { - await request("/input/click", { - method: "POST", - body: JSON.stringify({ x, y, button }), - }); -} - -export async function typeText(text: string): Promise { - await request("/input/type", { - method: "POST", - body: JSON.stringify({ text }), - }); -} - -export async function pressKey( - key: string, - modifiers: string[] = [], -): Promise { - await request("/input/key", { - method: "POST", - body: JSON.stringify({ key, modifiers }), - }); -} - -export async function scroll( - x: number, - y: number, - direction: string, - amount: number, -): Promise { - await request("/input/scroll", { - method: "POST", - body: JSON.stringify({ x, y, direction, amount }), - }); -} - -export async function drag( - fromX: number, - fromY: number, - toX: number, - toY: number, -): Promise { - await request("/input/drag", { - method: "POST", - body: JSON.stringify({ - from_x: fromX, - from_y: fromY, - to_x: toX, - to_y: toY, - }), - }); -} - -// ── Process ──────────────────────────────────────────── - -export async function spawnApp(path: string): Promise { - const res = await fetch(`${BASE()}/app/spawn`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ path }), - }); - if (!res.ok) { - const body = await res.text(); - throw new Error(`spawnApp failed: ${body}`); - } - return res.json(); -} - -export async function spawnCli( - command: string, - args: string[], - cols?: number, - rows?: number, -): Promise { - const body: Record = { command, args }; - if (cols !== undefined) body.cols = cols; - if (rows !== undefined) body.rows = rows; - const res = await fetch(`${BASE()}/cli/spawn`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - }); - if (!res.ok) { - const text = await res.text(); - throw new Error(`spawnCli failed: ${text}`); - } - return res.json(); -} - -export async function listProcesses(): Promise { - const res = await fetch(`${BASE()}/processes`); - return res.json(); -} - -export async function killProcess(pid: number): Promise { - await request("/process/kill", { - method: "POST", - body: JSON.stringify({ pid }), - }); -} - -// ── PTY WebSocket ────────────────────────────────────── - -function wsBaseUrl(): string { - const port = getPort(); - return `ws://127.0.0.1:${port}`; -} - -export interface PtyWsConnection { - ws: WebSocket; - onOutput: (cb: (data: string | Uint8Array) => void) => () => void; - onError: (cb: (msg: string) => void) => () => void; - onClose: (cb: (code: number, reason: string) => void) => () => void; - sendInput: (data: string) => void; - resize: (cols: number, rows: number) => void; - close: () => void; -} - -export function ptyConnectWs(pid: number): PtyWsConnection { - const ws = new WebSocket(`${wsBaseUrl()}/pty/ws/${pid}`); - ws.binaryType = "arraybuffer"; - const outputListeners: ((data: string | Uint8Array) => void)[] = []; - const errorListeners: ((msg: string) => void)[] = []; - const closeListeners: ((code: number, reason: string) => void)[] = []; - - ws.onopen = () => { - debugLog(`frontend: connected to /pty/ws/${pid}`); - }; - ws.onclose = (e) => { - debugLog(`frontend: connection closed, code=${e.code}, reason=${e.reason}`); - for (const cb of closeListeners) cb(e.code, e.reason); - }; - ws.onerror = () => { - const msg = `WebSocket connection to PTY ${pid} failed`; - debugError(`frontend: ${msg}`); - for (const cb of errorListeners) cb(msg); - }; - ws.onmessage = (e) => { - if (e.data instanceof ArrayBuffer) { - const u8 = new Uint8Array(e.data); - debugLog(`frontend: received binary message, len=${u8.length}`); - for (const cb of outputListeners) cb(u8); - } else if (typeof e.data === "string") { - const preview = e.data.length > 80 ? e.data.substring(0, 80) : e.data; - debugLog(`frontend: received text message, len=${e.data.length}, preview=${JSON.stringify(preview)}`); - for (const cb of outputListeners) cb(e.data); - } - }; - - return { - ws, - onOutput(cb) { - outputListeners.push(cb); - return () => { - const idx = outputListeners.indexOf(cb); - if (idx >= 0) outputListeners.splice(idx, 1); - }; - }, - onError(cb) { - errorListeners.push(cb); - return () => { - const idx = errorListeners.indexOf(cb); - if (idx >= 0) errorListeners.splice(idx, 1); - }; - }, - onClose(cb) { - closeListeners.push(cb); - return () => { - const idx = closeListeners.indexOf(cb); - if (idx >= 0) closeListeners.splice(idx, 1); - }; - }, - sendInput(data) { - if (ws.readyState === WebSocket.OPEN) ws.send(data); - }, - resize(cols, rows) { - if (ws.readyState === WebSocket.OPEN) - ws.send(JSON.stringify({ type: "resize", cols, rows })); - }, - close() { - ws.close(); - }, - }; -} - -// ── Windows ──────────────────────────────────────────── - -export async function listWindows(): Promise<[number, string][]> { - const res = await fetch(`${BASE()}/windows`); - return res.json(); -} diff --git a/sandbox-web/src/components/Dashboard.tsx b/sandbox-web/src/components/Dashboard.tsx deleted file mode 100644 index 57edb76..0000000 --- a/sandbox-web/src/components/Dashboard.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { type ReactNode } from "react"; -import SandboxTerminal from "./Terminal"; - -interface DashboardProps { - command: string; - connected: boolean; - activePid: number | null; - error?: string | null; - onSpawnReady?: (cols: number, rows: number) => void; - onScreenshot: () => void; - onWsError?: (msg: string) => void; - onWsClose?: (code: number, reason: string) => void; - children?: ReactNode; -} - -export default function Dashboard({ - command, - connected, - activePid, - error, - onSpawnReady, - onScreenshot, - onWsError, - onWsClose, - children, -}: DashboardProps) { - return ( -
- {/* Header — draggable region */} -
-

- Dashboard -

-
- - -
-
- - {/* Content */} -
- {/* Sandbox card */} -
- {/* Card header */} -
-
- - {">_"} - - - {command} (Sandboxed) - -
- -
- - {/* Terminal — fills remaining space */} -
- - {error && ( -
-
- - - -

- {error} -

-
-
- )} -
-
-
- - {/* Screenshot preview floating panel */} - {children} -
- ); -} - -function ResourceStats({ connected }: { connected: boolean }) { - return ( -
- - - -
- ); -} - -function Stat({ label, value }: { label: string; value: string }) { - return ( -
-
- {value} -
-
{label}
-
- ); -} diff --git a/sandbox-web/src/components/Sidebar.tsx b/sandbox-web/src/components/Sidebar.tsx deleted file mode 100644 index d024c53..0000000 --- a/sandbox-web/src/components/Sidebar.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useTheme } from "../themes/ThemeContext"; - -interface SidebarProps { - command: string; -} - -export default function Sidebar({ command }: SidebarProps) { - const { toggleTheme, theme } = useTheme(); - const isDark = theme.kind === "dark"; - - return ( - - ); -} diff --git a/sandbox-web/src/components/Terminal.tsx b/sandbox-web/src/components/Terminal.tsx deleted file mode 100644 index 9b03630..0000000 --- a/sandbox-web/src/components/Terminal.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { useEffect, useRef, useCallback } from "react"; -import { Terminal } from "@xterm/xterm"; -import { FitAddon } from "@xterm/addon-fit"; -import * as api from "../api"; -import { useTheme } from "../themes/ThemeContext"; -import type { TerminalTheme } from "../themes/types"; -import "@xterm/xterm/css/xterm.css"; - -interface TerminalProps { - activePid?: number | null; - onReady?: (cols: number, rows: number) => void; - onWsError?: (msg: string) => void; - onWsClose?: (code: number, reason: string) => void; -} - -function buildTerminalTheme(t: TerminalTheme): Record { - return { - background: t.background, - foreground: t.foreground, - cursor: t.cursor, - cursorAccent: t.cursorAccent, - selectionBackground: t.selectionBackground, - selectionForeground: t.selectionForeground, - black: t.black, - red: t.red, - green: t.green, - yellow: t.yellow, - blue: t.blue, - magenta: t.magenta, - cyan: t.cyan, - white: t.white, - brightBlack: t.brightBlack, - brightRed: t.brightRed, - brightGreen: t.brightGreen, - brightYellow: t.brightYellow, - brightBlue: t.brightBlue, - brightMagenta: t.brightMagenta, - brightCyan: t.brightCyan, - brightWhite: t.brightWhite, - }; -} - -/** - * Bypass xterm.js WriteBuffer's setTimeout-based scheduling which stalls in - * Tauri's WKWebView. Instead, call InputHandler.parse() directly and then - * fire the write-parsed event to trigger rendering. - */ -function writeDirect(term: Terminal, data: string | Uint8Array): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const core = (term as any)._core ?? (term as any).core; - if (!core) return; - const ih = core._inputHandler; - if (!ih || typeof ih.parse !== "function") return; - - // 防止单次解析过大数据导致主线程冻结 - const CHUNK_SIZE = 32 * 1024; // 32KB - if (typeof data === "string" && data.length > CHUNK_SIZE) { - for (let i = 0; i < data.length; i += CHUNK_SIZE) { - ih.parse(data.slice(i, i + CHUNK_SIZE), true); - } - } else if (data instanceof Uint8Array && data.length > CHUNK_SIZE) { - for (let i = 0; i < data.length; i += CHUNK_SIZE) { - ih.parse(data.slice(i, i + CHUNK_SIZE), true); - } - } else { - ih.parse(data, true); - } - - if (core._writeBuffer?._onWriteParsed) { - core._writeBuffer._onWriteParsed.fire(); - } -} - -export default function SandboxTerminal({ activePid = null, onReady, onWsError, onWsClose }: TerminalProps) { - const terminalRef = useRef(null); - const xtermRef = useRef(null); - const fitAddonRef = useRef(null); - const wsConnRef = useRef(null); - const activePidRef = useRef(activePid); - const onReadyRef = useRef(onReady); - const onWsErrorRef = useRef(onWsError); - const onWsCloseRef = useRef(onWsClose); - onReadyRef.current = onReady; - onWsErrorRef.current = onWsError; - onWsCloseRef.current = onWsClose; - const { theme } = useTheme(); - - // Keep activePidRef in sync so the resize handler (which closes over it) - // always reads the latest value without recreating the init effect. - useEffect(() => { - activePidRef.current = activePid; - }, [activePid]); - - // Initialize xterm.js once — theme updates in-place - useEffect(() => { - if (!terminalRef.current) return; - if (xtermRef.current) return; // already initialized - - const term = new Terminal({ - cursorBlink: true, - cursorStyle: "bar", - fontSize: 14, - fontFamily: - '"SF Mono", "Menlo", "Monaco", "Cascadia Code", "JetBrains Mono", monospace', - fontWeight: "400", - fontWeightBold: "600", - scrollback: 10000, - theme: buildTerminalTheme(theme.terminal), - allowProposedApi: true, - allowTransparency: true, - }); - - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - term.open(terminalRef.current); - fitAddon.fit(); - - // Notify parent of initial terminal size - onReadyRef.current?.(term.cols, term.rows); - - // Send keyboard input directly to the WebSocket connection - term.onData((data) => { - wsConnRef.current?.sendInput(data); - }); - - const handleResize = () => { - fitAddon.fit(); - const pid = activePidRef.current; - const conn = wsConnRef.current; - if (pid && conn) { - conn.resize(term.cols, term.rows); - } - }; - window.addEventListener("resize", handleResize); - - xtermRef.current = term; - fitAddonRef.current = fitAddon; - - return () => { - window.removeEventListener("resize", handleResize); - term.dispose(); - xtermRef.current = null; - }; - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - // Update terminal theme in-place without disposing - useEffect(() => { - if (!xtermRef.current) return; - const newTheme = buildTerminalTheme(theme.terminal); - xtermRef.current.options.theme = newTheme; - }, [theme.id]); - - // PTY WebSocket connection - useEffect(() => { - // Clean up previous connection - wsConnRef.current?.close(); - wsConnRef.current = null; - - if (activePid === null || activePid === undefined) return; - - const conn = api.ptyConnectWs(activePid); - wsConnRef.current = conn; - - const decoder = new TextDecoder(); - conn.onOutput((data) => { - const term = xtermRef.current; - if (!term) return; - const writeData = typeof data === "string" ? data : decoder.decode(data as Uint8Array); - writeDirect(term, writeData); - }); - - // Notify parent of WebSocket errors and closures - conn.onError((msg) => { - onWsErrorRef.current?.(msg); - }); - conn.onClose((code, reason) => { - onWsCloseRef.current?.(code, reason); - }); - - // Send initial resize so PTY matches xterm container size - const term = xtermRef.current; - if (term) { - const ws = conn.ws; - const sendResize = () => { - if (ws.readyState === WebSocket.OPEN) { - conn.resize(term.cols, term.rows); - } else { - setTimeout(sendResize, 100); - } - }; - sendResize(); - } - - return () => { - conn.close(); - wsConnRef.current = null; - }; - }, [activePid]); - - const containerRef = useCallback((node: HTMLDivElement | null) => { - if (node) { - requestAnimationFrame(() => fitAddonRef.current?.fit()); - } - }, []); - - return ( -
-
-
- ); -} diff --git a/sandbox-web/src/index.css b/sandbox-web/src/index.css deleted file mode 100644 index a304d6b..0000000 --- a/sandbox-web/src/index.css +++ /dev/null @@ -1,63 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body, #root { - width: 100%; - height: 100%; - overflow: hidden; - background: var(--sandbox-bg-primary); - font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* Tauri drag region */ -[data-tauri-drag-region] { - -webkit-app-region: drag; -} - -[data-tauri-drag-region] button, -[data-tauri-drag-region] a, -[data-tauri-drag-region] input { - -webkit-app-region: no-drag; -} - -/* xterm.js */ -.xterm { - padding: 8px 16px 12px 16px; - height: 100%; -} - -.xterm-viewport::-webkit-scrollbar { - width: 6px; -} - -.xterm-viewport::-webkit-scrollbar-track { - background: var(--sandbox-scrollbar-bg); -} - -.xterm-viewport::-webkit-scrollbar-thumb { - background: var(--sandbox-scrollbar-fg); - border-radius: 3px; -} - -.xterm-viewport::-webkit-scrollbar-thumb:hover { - background: var(--sandbox-scrollbar-fg); - opacity: 0.8; -} - -.xterm-cursor-layer { - transition: opacity 0.1s; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } -} diff --git a/sandbox-web/src/logger.ts b/sandbox-web/src/logger.ts deleted file mode 100644 index 5d2c9b4..0000000 --- a/sandbox-web/src/logger.ts +++ /dev/null @@ -1,30 +0,0 @@ -const isDebugEnabled = (): boolean => { - try { - // Vite exposes env vars via import.meta.env - const level = import.meta.env.VITE_LOG_LEVEL; - if (level) return level.toLowerCase() === "debug"; - // Also check URL param for quick toggle: ?log=debug - const params = new URLSearchParams(window.location.search); - return params.get("log") === "debug"; - } catch { - return false; - } -}; - -export function debugLog(...args: unknown[]): void { - if (isDebugEnabled()) { - console.log("[DEBUG-FE]", ...args); - } -} - -export function debugWarn(...args: unknown[]): void { - if (isDebugEnabled()) { - console.warn("[DEBUG-FE]", ...args); - } -} - -export function debugError(...args: unknown[]): void { - if (isDebugEnabled()) { - console.error("[DEBUG-FE]", ...args); - } -} diff --git a/sandbox-web/src/main.tsx b/sandbox-web/src/main.tsx deleted file mode 100644 index b668068..0000000 --- a/sandbox-web/src/main.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { useState, useCallback, useEffect, useRef } from "react"; -import ReactDOM from "react-dom/client"; -import { invoke } from "@tauri-apps/api/core"; -import Sidebar from "./components/Sidebar"; -import Dashboard from "./components/Dashboard"; -import { ThemeProvider } from "./themes/ThemeContext"; -import * as api from "./api"; -import "./index.css"; - -const isMac = - typeof navigator !== "undefined" && navigator.platform.startsWith("Mac"); - -function App() { - const [activePid, setActivePid] = useState(null); - const [connected, setConnected] = useState(false); - const [screenshotUrl, setScreenshotUrl] = useState(null); - const [showPreview, setShowPreview] = useState(false); - const [screenshotError, setScreenshotError] = useState(null); - const [command, setCommand] = useState("Sandbox"); - const [startupError, setStartupError] = useState(null); - const hasConnectedRef = useRef(false); - const emptyCountRef = useRef(0); - - // Auto-connect to spawned processes - useEffect(() => { - const pollProcesses = async () => { - try { - const list = await api.listProcesses(); - if (list.length > 0) { - emptyCountRef.current = 0; - setStartupError(null); - setConnected(true); - if (activePid === null && !hasConnectedRef.current) { - const running = list.find((p) => p.is_running); - if (running) { - setActivePid(running.pid); - hasConnectedRef.current = true; - } - } - } else { - setConnected(false); - emptyCountRef.current++; - if (emptyCountRef.current >= 5) { - setStartupError("Waiting for process to start..."); - } - } - } catch { - setConnected(false); - emptyCountRef.current++; - if (emptyCountRef.current >= 5) { - setStartupError("Failed to connect to sandbox server"); - } - } - }; - - pollProcesses(); - const interval = setInterval(pollProcesses, 2000); - return () => clearInterval(interval); - }, [activePid]); - - // Fetch command from Tauri sandbox config - useEffect(() => { - invoke<{ - command?: string; - mode?: string; - args?: string[]; - }>("get_sandbox_config") - .then((config) => { - if (config.command) { - const args = - config.args && config.args.length > 0 - ? " " + config.args.join(" ") - : ""; - setCommand(config.command + args); - } - }) - .catch(() => {}); - }, []); - - // Spawn pending CLI with correct terminal size - const handleSpawnReady = useCallback(async (cols: number, rows: number) => { - try { - const pending = await api.getPendingCli(); - if (pending.command) { - console.log(`[App] spawning pending CLI: ${pending.command} (${cols}x${rows})`); - await api.spawnCli(pending.command, pending.args || [], cols, rows); - } - } catch (err) { - console.error("[App] failed to spawn pending CLI:", err); - setStartupError( - `Failed to spawn process: ${err instanceof Error ? err.message : String(err)}` - ); - } - }, []); - - // WebSocket error/close handlers - const handleWsError = useCallback((msg: string) => { - setStartupError(msg); - }, []); - - const handleWsClose = useCallback((code: number, reason: string) => { - if (code !== 1000) { - setStartupError(`Terminal connection closed (${code})${reason ? `: ${reason}` : ""}`); - } - }, []); - - // Screenshot - const handleScreenshot = useCallback(async () => { - setScreenshotError(null); - try { - const url = await api.takeScreenshot(); - setScreenshotUrl(url); - setShowPreview(true); - } catch (err) { - setScreenshotError( - err instanceof Error ? err.message : "Screenshot failed", - ); - setTimeout(() => setScreenshotError(null), 4000); - } - }, []); - - const closePreview = useCallback(() => { - setShowPreview(false); - if (screenshotUrl) { - URL.revokeObjectURL(screenshotUrl); - setScreenshotUrl(null); - } - }, [screenshotUrl]); - - return ( -
- {/* macOS drag region — reserves space for traffic light buttons */} - {isMac && ( -
- )} - - - - {/* Screenshot error toast */} - {screenshotError && ( -
-
- - - - {screenshotError} -
-
- )} - - {/* Screenshot preview floating panel */} - {showPreview && screenshotUrl && ( -
-
-
- - Screenshot - - -
- Screenshot -
-
- )} -
-
- ); -} - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); diff --git a/sandbox-web/src/themes/ThemeContext.tsx b/sandbox-web/src/themes/ThemeContext.tsx deleted file mode 100644 index 9b676ad..0000000 --- a/sandbox-web/src/themes/ThemeContext.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { - createContext, - useContext, - useState, - useCallback, - useEffect, - type ReactNode, -} from "react"; -import type { SandboxTheme, TerminalTheme } from "./types"; -import { themeRegistry } from "./registry"; - -interface ThemeContextValue { - theme: SandboxTheme; - setTheme: (id: string) => void; - toggleTheme: () => void; - themes: SandboxTheme[]; -} - -const ThemeContext = createContext(null); - -const STORAGE_KEY = "sandbox-theme"; - -function applyThemeCSS(theme: SandboxTheme): void { - const root = document.documentElement; - root.setAttribute("data-theme", theme.id); - root.setAttribute("data-theme-kind", theme.kind); - - const c = theme.colors; - root.style.setProperty("--sandbox-bg-primary", c.bgPrimary); - root.style.setProperty("--sandbox-bg-secondary", c.bgSecondary); - root.style.setProperty("--sandbox-bg-tertiary", c.bgTertiary); - root.style.setProperty("--sandbox-fg-primary", c.fgPrimary); - root.style.setProperty("--sandbox-fg-secondary", c.fgSecondary); - root.style.setProperty("--sandbox-fg-tertiary", c.fgTertiary); - root.style.setProperty("--sandbox-border", c.border); - root.style.setProperty("--sandbox-accent", c.accent); - root.style.setProperty("--sandbox-scrollbar-bg", c.scrollbarBg); - root.style.setProperty("--sandbox-scrollbar-fg", c.scrollbarFg); - root.style.setProperty("--sandbox-success", c.success); - root.style.setProperty("--sandbox-error", c.error); - root.style.setProperty("--sandbox-titlebar-bg", c.titlebarBg); - root.style.setProperty("--sandbox-titlebar-fg", c.titlebarFg); - root.style.setProperty("--sandbox-sidebar-bg", c.sidebarBg); - root.style.setProperty("--sandbox-sidebar-fg", c.sidebarFg); - root.style.setProperty("--sandbox-sidebar-border", c.sidebarBorder); - root.style.setProperty("--sandbox-sidebar-active", c.sidebarActive); - root.style.setProperty("--sandbox-panel-bg", c.panelBg); -} - -export { ThemeContext, STORAGE_KEY }; -export type { ThemeContextValue, SandboxTheme, TerminalTheme }; - -export function ThemeProvider({ children }: { children: ReactNode }) { - const [theme, setThemeState] = useState(() => { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const t = themeRegistry.get(stored); - if (t) return t; - } - return themeRegistry.defaultTheme(); - }); - - useEffect(() => { - applyThemeCSS(theme); - }, [theme]); - - const setTheme = useCallback((id: string) => { - const t = themeRegistry.get(id); - if (t) { - setThemeState(t); - localStorage.setItem(STORAGE_KEY, id); - } - }, []); - - const toggleTheme = useCallback(() => { - const themes = themeRegistry.list(); - const idx = themes.findIndex((t) => t.id === theme.id); - const next = themes[(idx + 1) % themes.length]; - setTheme(next.id); - }, [theme.id, setTheme]); - - return ( - - {children} - - ); -} - -export function useTheme(): ThemeContextValue { - const ctx = useContext(ThemeContext); - if (!ctx) throw new Error("useTheme must be used within ThemeProvider"); - return ctx; -} diff --git a/sandbox-web/src/themes/registry.ts b/sandbox-web/src/themes/registry.ts deleted file mode 100644 index d16e3cb..0000000 --- a/sandbox-web/src/themes/registry.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SandboxTheme } from "./types"; -import { tokyoNight } from "./tokyo-night"; -import { vscodeLight } from "./vscode-light"; - -class ThemeRegistry { - private themes = new Map(); - - constructor() { - this.register(tokyoNight); - this.register(vscodeLight); - } - - register(theme: SandboxTheme): void { - this.themes.set(theme.id, theme); - } - - get(id: string): SandboxTheme | undefined { - return this.themes.get(id); - } - - list(): SandboxTheme[] { - return Array.from(this.themes.values()); - } - - defaultTheme(): SandboxTheme { - return tokyoNight; - } -} - -export const themeRegistry = new ThemeRegistry(); diff --git a/sandbox-web/src/themes/tokyo-night.ts b/sandbox-web/src/themes/tokyo-night.ts deleted file mode 100644 index 3948f1e..0000000 --- a/sandbox-web/src/themes/tokyo-night.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { SandboxTheme } from "./types"; - -export const tokyoNight: SandboxTheme = { - id: "tokyo-night", - name: "Tokyo Night", - kind: "dark", - colors: { - bgPrimary: "#1a1b26", - bgSecondary: "#24283b", - bgTertiary: "#3b4261", - fgPrimary: "#a9b1d6", - fgSecondary: "#565f89", - fgTertiary: "#414868", - border: "#3b4261", - accent: "#7aa2f7", - scrollbarBg: "transparent", - scrollbarFg: "rgba(255, 255, 255, 0.12)", - success: "#9ece6a", - error: "#f7768e", - titlebarBg: "#1f2233", - titlebarFg: "#a9b1d6", - sidebarBg: "#16161e", - sidebarFg: "#a9b1d6", - sidebarBorder: "#292e42", - sidebarActive: "rgba(122, 162, 247, 0.15)", - panelBg: "#1f2233", - }, - terminal: { - background: "#1a1b26", - foreground: "#a9b1d6", - cursor: "#c0caf5", - cursorAccent: "#1a1b26", - selectionBackground: "rgba(122, 162, 247, 0.3)", - selectionForeground: "#c0caf5", - black: "#15161e", - red: "#f7768e", - green: "#9ece6a", - yellow: "#e0af68", - blue: "#7aa2f7", - magenta: "#bb9af7", - cyan: "#7dcfff", - white: "#a9b1d6", - brightBlack: "#414868", - brightRed: "#f7768e", - brightGreen: "#9ece6a", - brightYellow: "#e0af68", - brightBlue: "#7aa2f7", - brightMagenta: "#bb9af7", - brightCyan: "#7dcfff", - brightWhite: "#c0caf5", - }, -}; diff --git a/sandbox-web/src/themes/types.ts b/sandbox-web/src/themes/types.ts deleted file mode 100644 index 1bc451f..0000000 --- a/sandbox-web/src/themes/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** VS Code-inspired theme system — color tokens prefixed with `--sandbox-` */ -export interface SandboxThemeColors { - bgPrimary: string; - bgSecondary: string; - bgTertiary: string; - fgPrimary: string; - fgSecondary: string; - fgTertiary: string; - border: string; - accent: string; - scrollbarBg: string; - scrollbarFg: string; - success: string; - error: string; - titlebarBg: string; - titlebarFg: string; - /** Sidebar background (typically dark even in light themes) */ - sidebarBg: string; - sidebarFg: string; - sidebarBorder: string; - sidebarActive: string; - /** Right detail panel background */ - panelBg: string; -} - -/** xterm.js terminal theme (subset of ITerminalOptions['theme']) */ -export interface TerminalTheme { - background: string; - foreground: string; - cursor: string; - cursorAccent: string; - selectionBackground: string; - selectionForeground: string; - black: string; - red: string; - green: string; - yellow: string; - blue: string; - magenta: string; - cyan: string; - white: string; - brightBlack: string; - brightRed: string; - brightGreen: string; - brightYellow: string; - brightBlue: string; - brightMagenta: string; - brightCyan: string; - brightWhite: string; -} - -export interface SandboxTheme { - id: string; - name: string; - kind: "dark" | "light"; - colors: SandboxThemeColors; - terminal: TerminalTheme; -} diff --git a/sandbox-web/src/themes/vscode-light.ts b/sandbox-web/src/themes/vscode-light.ts deleted file mode 100644 index 0818072..0000000 --- a/sandbox-web/src/themes/vscode-light.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { SandboxTheme } from "./types"; - -/** VS Code macOS light theme inspired palette */ -export const vscodeLight: SandboxTheme = { - id: "vscode-light", - name: "VS Code Light", - kind: "light", - colors: { - bgPrimary: "#f5f5f7", - bgSecondary: "#ffffff", - bgTertiary: "#e8e8e8", - fgPrimary: "#1d1d1f", - fgSecondary: "#6e6e73", - fgTertiary: "#aeaeb2", - border: "#d2d2d7", - accent: "#0071e3", - scrollbarBg: "transparent", - scrollbarFg: "rgba(0, 0, 0, 0.15)", - success: "#34c759", - error: "#ff3b30", - titlebarBg: "#e8e8ed", - titlebarFg: "#1d1d1f", - sidebarBg: "#1c1c2e", - sidebarFg: "#c7c7cc", - sidebarBorder: "#2c2c3e", - sidebarActive: "rgba(0, 113, 227, 0.2)", - panelBg: "#ffffff", - }, - terminal: { - background: "#1e1e2e", - foreground: "#cdd6f4", - cursor: "#f5e0dc", - cursorAccent: "#1e1e2e", - selectionBackground: "rgba(137, 180, 250, 0.3)", - selectionForeground: "#cdd6f4", - black: "#45475a", - red: "#f38ba8", - green: "#a6e3a1", - yellow: "#f9e2af", - blue: "#89b4fa", - magenta: "#f5c2e7", - cyan: "#94e2d5", - white: "#bac2de", - brightBlack: "#585b70", - brightRed: "#f38ba8", - brightGreen: "#a6e3a1", - brightYellow: "#f9e2af", - brightBlue: "#89b4fa", - brightMagenta: "#f5c2e7", - brightCyan: "#94e2d5", - brightWhite: "#a6adc8", - }, -}; diff --git a/sandbox-web/src/vite-env.d.ts b/sandbox-web/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/sandbox-web/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/sandbox-web/tailwind.config.js b/sandbox-web/tailwind.config.js deleted file mode 100644 index 5dfcd2a..0000000 --- a/sandbox-web/tailwind.config.js +++ /dev/null @@ -1,54 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: ["./index.html", "./src/**/*.{ts,tsx}"], - theme: { - extend: { - colors: { - sandbox: { - bg: { - primary: "var(--sandbox-bg-primary)", - secondary: "var(--sandbox-bg-secondary)", - tertiary: "var(--sandbox-bg-tertiary)", - }, - fg: { - primary: "var(--sandbox-fg-primary)", - secondary: "var(--sandbox-fg-secondary)", - tertiary: "var(--sandbox-fg-tertiary)", - }, - border: "var(--sandbox-border)", - accent: "var(--sandbox-accent)", - scrollbar: { - bg: "var(--sandbox-scrollbar-bg)", - fg: "var(--sandbox-scrollbar-fg)", - }, - success: "var(--sandbox-success)", - error: "var(--sandbox-error)", - titlebar: { - bg: "var(--sandbox-titlebar-bg)", - fg: "var(--sandbox-titlebar-fg)", - }, - sidebar: { - bg: "var(--sandbox-sidebar-bg)", - fg: "var(--sandbox-sidebar-fg)", - border: "var(--sandbox-sidebar-border)", - active: "var(--sandbox-sidebar-active)", - }, - panel: { - bg: "var(--sandbox-panel-bg)", - }, - }, - }, - fontFamily: { - mono: [ - '"SF Mono"', - '"Menlo"', - '"Monaco"', - '"Cascadia Code"', - '"JetBrains Mono"', - "monospace", - ], - }, - }, - }, - plugins: [], -}; diff --git a/sandbox-web/tsconfig.json b/sandbox-web/tsconfig.json deleted file mode 100644 index 61eb7c7..0000000 --- a/sandbox-web/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2021", - "useDefineForClassFields": true, - "lib": ["ES2021", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/sandbox-web/tsconfig.node.json b/sandbox-web/tsconfig.node.json deleted file mode 100644 index 42872c5..0000000 --- a/sandbox-web/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/sandbox-web/vite.config.ts b/sandbox-web/vite.config.ts deleted file mode 100644 index 32a91e5..0000000 --- a/sandbox-web/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], - clearScreen: false, - server: { - port: 5173, - strictPort: true, - }, -}); diff --git a/sandbox-web/vitest.config.ts b/sandbox-web/vitest.config.ts deleted file mode 100644 index d98cab1..0000000 --- a/sandbox-web/vitest.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from "vitest/config"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], - test: { - environmentMatchGlobs: [ - ["src/__tests__/**/*.{test,spec}.{ts,tsx}", "jsdom"], - ], - coverage: { - provider: "v8", - reporter: ["text", "json-summary"], - reportsDirectory: "./coverage", - include: ["src/**/*.{ts,tsx}"], - exclude: [ - "src/**/*.d.ts", - "src/main.tsx", - "src/__tests__/**", - "src/themes/types.ts", - ], - }, - setupFiles: ["src/__tests__/setup.ts"], - }, -}); diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml deleted file mode 100644 index cd1698b..0000000 --- a/src-tauri/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "system-test-sandbox" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -description = "macOS desktop automation sandbox (Tauri app)" - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -sandbox-core = { workspace = true, features = ["screencapturekit"] } -tauri = { version = "2", features = ["macos-private-api"] } -tauri-plugin-shell = "2" -serde.workspace = true -serde_json.workspace = true -tokio.workspace = true -tracing.workspace = true -axum.workspace = true -uuid.workspace = true diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist deleted file mode 100644 index 079c265..0000000 --- a/src-tauri/Info.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - NSScreenCaptureUsageDescription - System Test Sandbox requires screen capture permission to take automated screenshots of sandbox windows. - NSAppleEventsUsageDescription - System Test Sandbox requires accessibility permission to simulate mouse and keyboard input. - NSMicrophoneUsageDescription - System Test Sandbox requires microphone permission for audio capture capabilities. - - diff --git a/src-tauri/build.rs b/src-tauri/build.rs deleted file mode 100644 index 4576fd6..0000000 --- a/src-tauri/build.rs +++ /dev/null @@ -1,10 +0,0 @@ -fn main() { - tauri_build::build(); - - #[cfg(target_os = "macos")] - { - // Fix @rpath/libswift_Concurrency.dylib crash on macOS 26+ - println!("cargo:rustc-link-arg=-rpath"); - println!("cargo:rustc-link-arg=/usr/lib/swift"); - } -} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json deleted file mode 100644 index 207ad25..0000000 --- a/src-tauri/capabilities/default.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Default capabilities for System Test Sandbox", - "windows": ["main"], - "permissions": [ - "core:default", - "core:window:allow-set-title", - "core:window:allow-start-dragging", - "shell:allow-open" - ] -} diff --git a/src-tauri/entitlements.plist b/src-tauri/entitlements.plist deleted file mode 100644 index 0f05cde..0000000 --- a/src-tauri/entitlements.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation - - com.apple.developer.screen-capture - - - diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json deleted file mode 100644 index f43b852..0000000 --- a/src-tauri/gen/schemas/acl-manifests.json +++ /dev/null @@ -1 +0,0 @@ -{"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},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json deleted file mode 100644 index 5e991e8..0000000 --- a/src-tauri/gen/schemas/capabilities.json +++ /dev/null @@ -1 +0,0 @@ -{"default":{"identifier":"default","description":"Default capabilities for System Test Sandbox","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-set-title","core:window:allow-start-dragging","shell:allow-open"]}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json deleted file mode 100644 index d1e5361..0000000 --- a/src-tauri/gen/schemas/desktop-schema.json +++ /dev/null @@ -1,2612 +0,0 @@ -{ - "$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": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", - "type": "string", - "const": "shell:default", - "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute", - "markdownDescription": "Enables the execute command without any pre-configured scope." - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill", - "markdownDescription": "Enables the kill command without any pre-configured scope." - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open", - "markdownDescription": "Enables the open command without any pre-configured scope." - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn", - "markdownDescription": "Enables the spawn command without any pre-configured scope." - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write", - "markdownDescription": "Enables the stdin_write command without any pre-configured scope." - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute", - "markdownDescription": "Denies the execute command without any pre-configured scope." - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill", - "markdownDescription": "Denies the kill command without any pre-configured scope." - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open", - "markdownDescription": "Denies the open command without any pre-configured scope." - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn", - "markdownDescription": "Denies the spawn command without any pre-configured scope." - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write", - "markdownDescription": "Denies the stdin_write command without any pre-configured scope." - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "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." - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", - "type": "string", - "const": "shell:default", - "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute", - "markdownDescription": "Enables the execute command without any pre-configured scope." - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill", - "markdownDescription": "Enables the kill command without any pre-configured scope." - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open", - "markdownDescription": "Enables the open command without any pre-configured scope." - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn", - "markdownDescription": "Enables the spawn command without any pre-configured scope." - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write", - "markdownDescription": "Enables the stdin_write command without any pre-configured scope." - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute", - "markdownDescription": "Denies the execute command without any pre-configured scope." - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill", - "markdownDescription": "Denies the kill command without any pre-configured scope." - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open", - "markdownDescription": "Denies the open command without any pre-configured scope." - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn", - "markdownDescription": "Denies the spawn command without any pre-configured scope." - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write", - "markdownDescription": "Denies the stdin_write 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" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json deleted file mode 100644 index d1e5361..0000000 --- a/src-tauri/gen/schemas/macOS-schema.json +++ /dev/null @@ -1,2612 +0,0 @@ -{ - "$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": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", - "type": "string", - "const": "shell:default", - "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute", - "markdownDescription": "Enables the execute command without any pre-configured scope." - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill", - "markdownDescription": "Enables the kill command without any pre-configured scope." - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open", - "markdownDescription": "Enables the open command without any pre-configured scope." - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn", - "markdownDescription": "Enables the spawn command without any pre-configured scope." - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write", - "markdownDescription": "Enables the stdin_write command without any pre-configured scope." - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute", - "markdownDescription": "Denies the execute command without any pre-configured scope." - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill", - "markdownDescription": "Denies the kill command without any pre-configured scope." - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open", - "markdownDescription": "Denies the open command without any pre-configured scope." - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn", - "markdownDescription": "Denies the spawn command without any pre-configured scope." - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write", - "markdownDescription": "Denies the stdin_write command without any pre-configured scope." - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "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." - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", - "type": "string", - "const": "shell:default", - "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute", - "markdownDescription": "Enables the execute command without any pre-configured scope." - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill", - "markdownDescription": "Enables the kill command without any pre-configured scope." - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open", - "markdownDescription": "Enables the open command without any pre-configured scope." - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn", - "markdownDescription": "Enables the spawn command without any pre-configured scope." - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write", - "markdownDescription": "Enables the stdin_write command without any pre-configured scope." - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute", - "markdownDescription": "Denies the execute command without any pre-configured scope." - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill", - "markdownDescription": "Denies the kill command without any pre-configured scope." - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open", - "markdownDescription": "Denies the open command without any pre-configured scope." - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn", - "markdownDescription": "Denies the spawn command without any pre-configured scope." - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write", - "markdownDescription": "Denies the stdin_write 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" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png deleted file mode 100644 index 1ec73c0..0000000 Binary files a/src-tauri/icons/128x128.png and /dev/null differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png deleted file mode 100644 index d580eaf..0000000 Binary files a/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png deleted file mode 100644 index f09f6a6..0000000 Binary files a/src-tauri/icons/32x32.png and /dev/null differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns deleted file mode 100644 index 1ec73c0..0000000 Binary files a/src-tauri/icons/icon.icns and /dev/null differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico deleted file mode 100644 index f09f6a6..0000000 Binary files a/src-tauri/icons/icon.ico and /dev/null differ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs deleted file mode 100644 index 6e37af1..0000000 --- a/src-tauri/src/main.rs +++ /dev/null @@ -1,615 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -use sandbox_core::instance::{InstanceKind, InstanceRegistry, SandboxInstance}; -use sandbox_core::sandbox::{Sandbox, SandboxConfig}; -use sandbox_core::server::PendingCli; -use std::sync::{Arc, Mutex}; -use std::time::Instant; -use tauri::Manager; - -#[allow(dead_code)] -struct AppState { - sandbox: Mutex, - sandbox_id: Option, - port: Option, - kind: Option, -} - -#[tauri::command] -fn get_sandbox_state(state: tauri::State) -> Result { - let sandbox = state.sandbox.lock().map_err(|e| e.to_string())?; - let s = sandbox.state(); - Ok(serde_json::json!({ - "sandbox_id": s.sandbox_id, - "port": s.port, - "window_id": s.window_id, - "is_running": s.is_running, - "uptime_secs": sandbox.uptime_secs(), - })) -} - -#[tauri::command] -fn take_screenshot(state: tauri::State) -> Result, String> { - let sandbox = state.sandbox.lock().map_err(|e| e.to_string())?; - sandbox.screenshot().map_err(|e| e.to_string()) -} - -#[tauri::command] -fn get_sandbox_config(state: tauri::State) -> Result { - let sandbox = state.sandbox.lock().map_err(|e| e.to_string())?; - Ok(sandbox.config().clone()) -} - -#[tauri::command] -fn init_sandbox(state: tauri::State, window_id: u32) -> Result<(), String> { - let mut sandbox = state.sandbox.lock().map_err(|e| e.to_string())?; - sandbox.init(window_id).map_err(|e| e.to_string()) -} - -#[derive(Debug, Default)] -struct SandboxLaunchArgs { - sandbox_id: Option, - sandbox_port: Option, - mode: Option, - cmd: Option, - args: Vec, -} - -fn parse_sandbox_args_from_slice(args: &[String]) -> SandboxLaunchArgs { - let mut result = SandboxLaunchArgs::default(); - let mut i = 1; - while i < args.len() { - let arg = &args[i]; - if let Some(val) = arg.strip_prefix("--sandbox-id=") { - result.sandbox_id = Some(val.to_string()); - } else if arg == "--sandbox-id" && i + 1 < args.len() { - i += 1; - result.sandbox_id = Some(args[i].clone()); - } else if let Some(val) = arg.strip_prefix("--sandbox-port=") { - result.sandbox_port = val.parse().ok(); - } else if arg == "--sandbox-port" && i + 1 < args.len() { - i += 1; - result.sandbox_port = args[i].parse().ok(); - } else if let Some(val) = arg.strip_prefix("--mode=") { - result.mode = Some(val.to_string()); - } else if arg == "--mode" && i + 1 < args.len() { - i += 1; - result.mode = Some(args[i].clone()); - } else if let Some(val) = arg.strip_prefix("--cmd=") { - result.cmd = Some(val.to_string()); - } else if arg == "--cmd" && i + 1 < args.len() { - i += 1; - result.cmd = Some(args[i].clone()); - } else if arg == "--" { - result.args = args[(i + 1)..].to_vec(); - break; - } - i += 1; - } - result -} - -fn parse_sandbox_args() -> SandboxLaunchArgs { - let args: Vec = std::env::args().collect(); - tracing::info!("[args] raw args: {:?}", args); - let result = parse_sandbox_args_from_slice(&args); - tracing::info!( - "[args] parsed: mode={:?}, cmd={:?}, args={:?}, sandbox_id={:?}, port={:?}", - result.mode, - result.cmd, - result.args, - result.sandbox_id, - result.sandbox_port, - ); - result -} - -fn main() { - let launch_args = parse_sandbox_args(); - - // Auto-generate sandbox_id and port if not provided - let sandbox_id = launch_args - .sandbox_id - .clone() - .or_else(|| Some(uuid::Uuid::new_v4().to_string()[..8].to_string())); - let sandbox_port = launch_args.sandbox_port.or(Some(5801)); - - // Initialize file logging after sandbox_id is known - let (_sandbox_guard, _server_guard) = - sandbox_core::logging::init_sandbox_logging(sandbox_id.as_deref().unwrap_or("unknown")); - - let mode = launch_args.mode.clone().or_else(|| Some("cli".to_string())); - let cmd = launch_args.cmd.clone().or_else(|| Some("zsh".to_string())); - - let config = SandboxConfig { - id: launch_args.sandbox_id.clone(), - port: launch_args.sandbox_port, - mode: mode.clone(), - command: cmd.clone(), - args: launch_args.args.clone(), - ..SandboxConfig::default() - }; - - let kind = match (mode.as_deref(), &cmd) { - (Some("cli"), Some(cmd)) => Some(InstanceKind::Cli { - command: cmd.clone(), - args: launch_args.args.clone(), - }), - (Some("app"), Some(path)) => Some(InstanceKind::App { path: path.clone() }), - _ => None, - }; - - // Title is intentionally empty — command name is shown in the Dashboard header. - let title = String::new(); - - let kind_for_setup = kind.clone(); - let sandbox_id_for_close = sandbox_id.clone(); - let port_for_close = sandbox_port; - - tauri::Builder::default() - .plugin(tauri_plugin_shell::init()) - .manage(AppState { - sandbox: Mutex::new(Sandbox::new(config)), - sandbox_id: sandbox_id.clone(), - port: sandbox_port, - kind: kind.clone(), - }) - .invoke_handler(tauri::generate_handler![ - get_sandbox_state, - take_screenshot, - get_sandbox_config, - init_sandbox, - ]) - .setup(move |app_handle| { - tracing::info!( - "[setup] sandbox_id={:?}, port={:?}, kind={:?}", - sandbox_id, - sandbox_port, - kind - ); - - // Set window title - if let Some(window) = app_handle.get_webview_window("main") { - tracing::info!("[setup] setting window title: {}", title); - let _ = window.set_title(&title); - } else { - tracing::warn!("[setup] main window not found!"); - } - - // Start embedded HTTP server if in managed mode - if let (Some(id), Some(port)) = (&sandbox_id, sandbox_port) { - let pending_cli = if let Some(InstanceKind::Cli { command, args }) = &kind { - Some(PendingCli { - command: command.clone(), - args: args.clone(), - }) - } else { - None - }; - - let state = Arc::new(tokio::sync::Mutex::new(sandbox_core::server::AppState { - sandbox_id: Some(id.clone()), - start_time: Instant::now(), - window_id: None, - target_pid: Some(std::process::id()), - pending_cli, - })); - - // Clone for window discovery task - let state_for_window = state.clone(); - - let router = sandbox_core::server::build_router(state); - let port_val = port; - let sandbox_id_for_server = sandbox_id.clone(); - - tauri::async_runtime::spawn(async move { - let addr = format!("127.0.0.1:{port_val}"); - match tokio::net::TcpListener::bind(&addr).await { - Ok(listener) => { - tracing::info!("Sandbox HTTP API listening on http://{addr}"); - if let Err(e) = axum::serve(listener, router).await { - tracing::error!("HTTP server error: {e}"); - } - } - Err(e) => { - tracing::error!("Failed to bind HTTP server on port {port_val}: {e}"); - if let Some(ref id) = sandbox_id_for_server { - let registry = - sandbox_core::instance::InstanceRegistry::default(); - let _ = registry.update_status( - id, - sandbox_core::instance::InstanceStatus::Error(format!( - "HTTP bind failed: {e}" - )), - ); - } - } - } - }); - - // Register instance - let registry = InstanceRegistry::default(); - let instance = SandboxInstance::new( - id.clone(), - port, - std::process::id(), - kind_for_setup.unwrap_or(InstanceKind::Cli { - command: "unknown".into(), - args: vec![], - }), - ); - if let Err(e) = registry.register(&instance) { - tracing::error!("Failed to register instance: {e}"); - } - - // CLI spawn is now deferred — frontend queries /sandbox/pending-cli - // and spawns with the correct terminal size. - - // Auto-discover the Tauri window's SCWindow ID for screenshot support. - // The window needs time to render before ScreenCaptureKit can find it. - let own_pid = std::process::id(); - tauri::async_runtime::spawn(async move { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - tracing::info!("[setup] discovering window by PID {own_pid}"); - match sandbox_core::capture::ScreenCapture::find_window_by_pid(own_pid) { - Ok(id) => { - tracing::info!("[setup] discovered sandbox window: SCWindow ID={id}"); - state_for_window.lock().await.window_id = Some(id); - } - Err(e) => { - tracing::warn!("[setup] failed to discover sandbox window by PID: {e}"); - // Fallback: try title-based discovery - match sandbox_core::capture::ScreenCapture::find_window_by_title( - "System Test Sandbox", - ) { - Ok(id) => { - tracing::info!( - "[setup] discovered sandbox window by title: SCWindow ID={id}" - ); - state_for_window.lock().await.window_id = Some(id); - } - Err(e2) => { - tracing::warn!( - "[setup] title-based discovery also failed: {e2}" - ); - if let Ok(windows) = - sandbox_core::capture::ScreenCapture::list_windows() - { - for (wid, wtitle) in &windows { - if !wtitle.is_empty() { - tracing::info!( - "[setup] window {}: '{}'", - wid, - wtitle - ); - } - } - } - } - } - } - } - }); - } - - // Window close cleanup - if let Some(window) = app_handle.get_webview_window("main") { - let close_id = sandbox_id_for_close.clone(); - let _close_port = port_for_close; - window.on_window_event(move |event| { - if let tauri::WindowEvent::CloseRequested { .. } = event { - if let Some(ref id) = close_id { - tracing::info!("Sandbox window closing, cleaning up instance {id}"); - let registry = InstanceRegistry::default(); - let _ = registry.unregister(id); - tracing::info!("Instance {id} unregistered"); - } - } - }); - } - - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn args(s: &[&str]) -> Vec { - s.iter().map(|s| s.to_string()).collect() - } - - // ── parse_sandbox_args_from_slice ────────────────────── - - #[test] - fn parse_mode_and_cmd_eq() { - let r = parse_sandbox_args_from_slice(&args(&["bin", "--mode=cli", "--cmd=claude"])); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("claude")); - assert!(r.args.is_empty()); - } - - #[test] - fn parse_mode_and_cmd_space() { - let r = parse_sandbox_args_from_slice(&args(&["bin", "--mode", "cli", "--cmd", "claude"])); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("claude")); - } - - #[test] - fn parse_trailing_args_after_separator() { - let r = parse_sandbox_args_from_slice(&args(&[ - "bin", - "--mode=cli", - "--cmd=claude", - "--", - "-p", - "你是谁?", - ])); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("claude")); - assert_eq!(r.args, vec!["-p", "你是谁?"]); - } - - #[test] - fn parse_no_trailing_args() { - let r = parse_sandbox_args_from_slice(&args(&["bin", "--mode=cli", "--cmd=zsh"])); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("zsh")); - assert!(r.args.is_empty()); - } - - #[test] - fn parse_sandbox_id_and_port() { - let r = parse_sandbox_args_from_slice(&args(&[ - "bin", - "--sandbox-id=abc123", - "--sandbox-port=15801", - "--mode=cli", - "--cmd=echo", - ])); - assert_eq!(r.sandbox_id.as_deref(), Some("abc123")); - assert_eq!(r.sandbox_port, Some(15801)); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("echo")); - } - - #[test] - fn parse_empty_args() { - let r = parse_sandbox_args_from_slice(&args(&["bin"])); - assert!(r.mode.is_none()); - assert!(r.cmd.is_none()); - assert!(r.args.is_empty()); - } - - #[test] - fn parse_only_cmd_no_mode() { - let r = parse_sandbox_args_from_slice(&args(&["bin", "--cmd=claude"])); - assert!(r.mode.is_none()); - assert_eq!(r.cmd.as_deref(), Some("claude")); - } - - #[test] - fn parse_mixed_eq_and_space() { - let r = parse_sandbox_args_from_slice(&args(&[ - "bin", - "--mode=cli", - "--cmd", - "claude", - "--", - "-p", - "hello", - ])); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("claude")); - assert_eq!(r.args, vec!["-p", "hello"]); - } - - // ── SandboxConfig construction from parsed args ─────── - - #[test] - fn config_from_parsed_cli_args() { - let r = parse_sandbox_args_from_slice(&args(&[ - "bin", - "--sandbox-id=test1", - "--sandbox-port=5801", - "--mode=cli", - "--cmd=claude", - "--", - "-p", - "你是谁?", - ])); - - let config = SandboxConfig { - id: r.sandbox_id.clone(), - port: r.sandbox_port, - mode: r.mode.clone(), - command: r.cmd.clone(), - args: r.args.clone(), - ..SandboxConfig::default() - }; - - assert_eq!(config.id.as_deref(), Some("test1")); - assert_eq!(config.port, Some(5801)); - assert_eq!(config.mode.as_deref(), Some("cli")); - assert_eq!(config.command.as_deref(), Some("claude")); - assert_eq!(config.args, vec!["-p", "你是谁?"]); - } - - #[test] - fn kind_from_config_cli() { - let config = SandboxConfig { - mode: Some("cli".into()), - command: Some("claude".into()), - args: vec!["-p".into(), "你是谁?".into()], - ..SandboxConfig::default() - }; - let sandbox = Sandbox::new(config); - let kind = sandbox.kind().unwrap(); - match kind { - InstanceKind::Cli { command, args } => { - assert_eq!(command, "claude"); - assert_eq!(args, vec!["-p", "你是谁?"]); - } - _ => panic!("Expected CLI kind"), - } - } - - #[test] - fn kind_from_config_app() { - let config = SandboxConfig { - mode: Some("app".into()), - command: Some("/Applications/Safari.app".into()), - ..SandboxConfig::default() - }; - let sandbox = Sandbox::new(config); - let kind = sandbox.kind().unwrap(); - assert!(matches!(kind, InstanceKind::App { .. })); - } - - #[test] - fn kind_none_without_mode() { - let config = SandboxConfig::default(); - let sandbox = Sandbox::new(config); - assert!(sandbox.kind().is_none()); - } - - // ── CLI args construction (mirrors cmd_start logic) ─── - - #[test] - fn cli_builds_tauri_args_simple() { - let command = "claude"; - let cli_args: Vec = vec![]; - - let mut tauri_args = vec!["--mode=cli".to_string(), format!("--cmd={}", command)]; - if !cli_args.is_empty() { - tauri_args.push("--".to_string()); - tauri_args.extend(cli_args.iter().cloned()); - } - - assert_eq!(tauri_args, vec!["--mode=cli", "--cmd=claude"]); - } - - #[test] - fn cli_builds_tauri_args_with_trailing() { - let command = "claude"; - let cli_args: Vec = vec!["-p".into(), "你是谁?".into()]; - - let mut tauri_args = vec!["--mode=cli".to_string(), format!("--cmd={}", command)]; - if !cli_args.is_empty() { - tauri_args.push("--".to_string()); - tauri_args.extend(cli_args.iter().cloned()); - } - - assert_eq!( - tauri_args, - vec!["--mode=cli", "--cmd=claude", "--", "-p", "你是谁?"] - ); - } - - #[test] - fn cli_builds_tauri_args_zsh() { - let command = "zsh"; - let cli_args: Vec = vec![]; - - let mut tauri_args = vec!["--mode=cli".to_string(), format!("--cmd={}", command)]; - if !cli_args.is_empty() { - tauri_args.push("--".to_string()); - tauri_args.extend(cli_args.iter().cloned()); - } - - assert_eq!(tauri_args, vec!["--mode=cli", "--cmd=zsh"]); - } - - // ── Round-trip: CLI builds args → Tauri parses them ─── - - #[test] - fn roundtrip_claude_with_args() { - // CLI constructs these args - let command = "claude"; - let cli_args = vec!["-p".to_string(), "你是谁?".to_string()]; - let mut tauri_args = vec!["--mode=cli".to_string(), format!("--cmd={}", command)]; - tauri_args.push("--".to_string()); - tauri_args.extend(cli_args); - - // Prepend program name (as std::env::args would) - let mut full_args = vec!["system-test-sandbox".to_string()]; - full_args.extend(tauri_args); - - // Tauri parses them - let r = parse_sandbox_args_from_slice(&full_args); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("claude")); - assert_eq!(r.args, vec!["-p", "你是谁?"]); - } - - #[test] - fn roundtrip_simple_command() { - let command = "zsh"; - let cli_args: Vec = vec![]; - let mut tauri_args = vec!["--mode=cli".to_string(), format!("--cmd={}", command)]; - if !cli_args.is_empty() { - tauri_args.push("--".to_string()); - tauri_args.extend(cli_args); - } - - let mut full_args = vec!["system-test-sandbox".to_string()]; - full_args.extend(tauri_args); - - let r = parse_sandbox_args_from_slice(&full_args); - assert_eq!(r.mode.as_deref(), Some("cli")); - assert_eq!(r.cmd.as_deref(), Some("zsh")); - assert!(r.args.is_empty()); - } - - // ── Window discovery: title is empty, PID-based discovery is required ── - - #[test] - fn window_title_is_intentionally_empty() { - // Verify the title is set to empty string (not "System Test Sandbox") - let title = String::new(); - assert!(title.is_empty()); - assert_ne!(title, "System Test Sandbox"); - } - - #[test] - fn own_pid_is_valid() { - let pid = std::process::id(); - assert!(pid > 0, "Process ID should be positive"); - } - - #[test] - fn find_window_by_pid_nonexistent_returns_error() { - // Verify that the PID-based discovery properly returns errors - // for non-existent PIDs (this mirrors the actual setup code path) - let result = sandbox_core::capture::ScreenCapture::find_window_by_pid(9999999); - assert!(result.is_err()); - } - - #[test] - fn find_window_by_title_empty_string_returns_error() { - // Verify that searching by empty title fails (since title is now "") - let result = sandbox_core::capture::ScreenCapture::find_window_by_title(""); - // Empty string matches any window with empty title, but the Tauri window - // is unlikely to be discoverable via this path in test context - // The key point: this should NOT be the discovery mechanism - let _ = result; - } - - #[test] - fn pid_based_discovery_preferred_over_title() { - // This test documents the discovery strategy: - // 1. Try find_window_by_pid(own_pid) first - // 2. Fallback to find_window_by_title("System Test Sandbox") - // Since title is empty, step 1 is essential. - let pid = std::process::id(); - let title = String::new(); - // Title discovery won't work with empty title - assert!(title.is_empty()); - // PID is always available and positive - assert!(pid > 0); - } -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json deleted file mode 100644 index 1935d96..0000000 --- a/src-tauri/tauri.conf.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json", - "productName": "System Test Sandbox", - "version": "0.1.0", - "identifier": "com.system-test-sandbox", - "build": { - "frontendDist": "../sandbox-web/dist", - "devUrl": "http://localhost:5173", - "beforeDevCommand": "cd /Users/zn-ice/2026/system-test-sandbox/sandbox-web && pnpm dev", - "beforeBuildCommand": "cd /Users/zn-ice/2026/system-test-sandbox/sandbox-web && pnpm build" - }, - "app": { - "windows": [ - { - "label": "main", - "title": "", - "width": 1280, - "height": 800, - "minWidth": 800, - "minHeight": 500, - "resizable": true, - "titleBarStyle": "Overlay", - "center": true - } - ], - "security": { - "csp": null - }, - "macOSPrivateApi": true - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png" - ], - "macOS": { - "entitlements": "./entitlements.plist" - } - } -} diff --git a/tests/release_test.md b/tests/release_test.md index 199500e..1c1c77f 100644 --- a/tests/release_test.md +++ b/tests/release_test.md @@ -1,13 +1,14 @@ -/superpowers:systematic-debugging 测试一下,release.sh编译构建后,使用release中的cli打开opencode,然后使用cli输入"你是谁?", - 并出发回车发送。这个过程每一步都截图分析,保存到@release_test/${{yyyy-mm-dd hh-mm-ss}}文件夹下。 - -/superpowers:systematic-debugging 使用 @release.sh - 打包编译release,然后你进行一个简单场景的测试,场景一:在沙箱启动claude以后,回车确认,然后输入你是谁? - 然后出发回车发送。场景二:在沙箱启动zsh,然后输入echo "hello - world",然后回车发送。每一步操作后都截图保存到release_test/${{时间戳,yyyy - -mm-dd-hh-mm-ss}}文件夹下,然后检查截图结果,查看是否符合预期,注意读取图片前先判断图片是否存在问题 - -## 当前存在问题的场景 - -- /superpowers:systematic-debugging 使用 @release.sh 打包编译release,然后先打开opencode,再打开zsh,会发现两个窗口都是opencode的界面 -- 当前打开的claude,在回车确认后,界面上会有选项`Yes, I trust this folder`残留 \ No newline at end of file +使用 @README.md 中的cli命令完成下面场景的验证: +1. /superpowers:systematic-debugging 测试一下,release.sh编译构建后,使用release中的cli打开opencode,然后使用cli输入"你是谁?", 并出发回车发送。这个过程每一步都截图分析,保存到@release_test/${{yyyy-mm-dd hh-mm-ss}}文件夹下。 +2. /superpowers:systematic-debugging 使用 @release.sh 打包编译release,然后你进行一个简单场景的测试,场景一:在沙箱启动claude以后,回车确认,然后输入你是谁?然后出发回车发送。场景二:在沙箱启动zsh,然后输入echo "hello world",然后回车发送。每一步操作后都截图保存到release_test/${{时间戳,yyyy-mm-dd-hh-mm-ss}}文件夹下,然后检查截图结果,查看是否符合预期,注意读取图片前先判断图片是否存在问题 +3. /superpowers:systematic-debugging 使用 @release.sh 打包编译release,然后先打开opencode,再打开zsh,分别CLI命令行截图两个窗口,获取到的是各自的界面 +4. 当前打开的claude,在回车确认后,判断界面上,不会有选项`Yes, I trust this folder`残留 +5. 新增测试点 + - pnpm dev 后 Electron 窗口正常打开 + - 终端日志显示 "Daemon started on port XXXX" + - sandbox start zsh 创建新 Tab,xterm.js 显示 zsh 提示符 + - sandbox start claude 创建另一个 Tab,显示 Claude Code + - Tab 切换正常(离屏定位策略) + - 截图功能正常 + - 关闭一个 Tab 不影响其他 Tab +6. 在release_test/${{时间戳,yyyy-mm-dd-hh-mm-ss}}文件夹下,生成markdown的最终测试报告 \ No newline at end of file