From 45d8d4f4e4fb1c1860f17a6289e2bedd02274777 Mon Sep 17 00:00:00 2001 From: JSRCode <139555610+jsrcode@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:47:19 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(onboarding):=20=E9=87=8D=E6=9E=84=20v3?= =?UTF-8?q?=20=E5=BC=95=E5=AF=BC=E6=B5=81=E7=A8=8B=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=B7=A5=E5=85=B7=E5=AE=9E=E4=BE=8B=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **主要变更:** 1. **后端能力增强** - 新增 validate_tool_path 命令:验证用户指定的工具路径有效性 - 新增 add_manual_tool_instance 命令:支持保存用户手动指定的工具路径 - 新增 detect_tool_without_save 命令:支持检测预览(不保存到数据库) - 修改 refresh_tool_status 行为:从数据库读取而非重新检测(性能优化) 2. **v3 引导流程重构** - ToolDetectionStep:从自动检测改为引导用户进入工具管理页面 - 使用标准化的 onboarding-navigate 事件处理页面跳转和限制 - 支持自动触发添加实例对话框(autoAction 机制) 3. **工具管理 UI 增强** - AddInstanceDialog:支持本地环境的两种添加方式 - 自动扫描:检测系统已安装的工具 - 手动指定:浏览文件选择器指定工具路径 - 集成 @tauri-apps/plugin-dialog 用于原生文件选择器 - 实时验证路径有效性和显示检测结果 4. **导航和限制机制** - App.tsx:支持 restrictedPage 限制用户在引导过程中的导航 - 监听 onboarding-navigate 和 clear-onboarding-restriction 事件 - 延迟触发自动动作(500ms)确保页面渲染完成 5. **类型和依赖更新** - 新增 OnboardingNavigationPayload 类型定义 - 添加 @tauri-apps/plugin-dialog 依赖 - 更新 Tauri schemas 以支持新命令 **改进点:** - 用户体验:从被动的自动检测改为主动的引导式配置 - 灵活性:支持自动扫描和手动指定两种方式 - 性能:检测和保存分离,避免不必要的重复检测 - 可维护性:标准化事件处理和页面导航逻辑 **影响范围:** - 前端:引导流程、工具管理页面、事件系统 - 后端:工具检测命令、数据库操作逻辑 - 依赖:新增原生对话框插件 --- package-lock.json | 10 + package.json | 1 + src-tauri/Cargo.lock | 213 ++++++- src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 2 +- src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/gen/schemas/capabilities.json | 2 +- src-tauri/gen/schemas/desktop-schema.json | 66 +++ src-tauri/gen/schemas/windows-schema.json | 66 +++ src-tauri/src/commands/tool_commands.rs | 248 +++++++- src-tauri/src/commands/tool_management.rs | 21 +- src-tauri/src/commands/types.rs | 10 + src-tauri/src/main.rs | 7 +- src-tauri/src/services/tool/registry.rs | 95 ++- src/App.tsx | 52 +- .../Onboarding/steps/v3/ToolDetectionStep.tsx | 242 +++----- src/components/layout/AppSidebar.tsx | 27 +- src/lib/tauri-commands.ts | 42 ++ .../components/ManualPathDialog.tsx | 219 +++++++ .../components/AddInstanceDialog.tsx | 543 ++++++++++++++++-- .../hooks/useToolManagement.ts | 19 +- src/pages/ToolManagementPage/index.tsx | 39 +- src/types/onboarding.ts | 12 + 23 files changed, 1638 insertions(+), 301 deletions(-) create mode 100644 src/pages/Onboarding/components/ManualPathDialog.tsx diff --git a/package-lock.json b/package-lock.json index 32a1ae0..236adbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.8", "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-shell": "^2.3.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -3070,6 +3071,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.2.tgz", + "integrity": "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-shell": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.3.tgz", diff --git a/package.json b/package.json index 1b8ec37..1930898 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.8", "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-shell": "^2.3.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 12656b7..cfd75eb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -59,6 +59,27 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -781,6 +802,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", + "libc", "objc2 0.6.3", ] @@ -795,6 +818,15 @@ dependencies = [ "syn 2.0.109", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "dlopen2" version = "0.8.0" @@ -818,6 +850,12 @@ dependencies = [ "syn 2.0.109", ] +[[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" @@ -873,6 +911,7 @@ dependencies = [ "sha2", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-shell", "tauri-plugin-single-instance", "tempfile", @@ -3080,7 +3119,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.12.0", - "quick-xml", + "quick-xml 0.38.3", "serde", "time", ] @@ -3210,6 +3249,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.38.3" @@ -3259,6 +3307,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -3279,6 +3337,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[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 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3297,6 +3365,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3433,6 +3510,31 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "ring" version = "0.17.14" @@ -3606,6 +3708,12 @@ dependencies = [ "syn 2.0.109", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4343,6 +4451,46 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "toml 0.9.8", + "url", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.3" @@ -4607,6 +4755,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.61.2", ] @@ -5213,6 +5362,66 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.10.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.82" @@ -5967,6 +6176,7 @@ dependencies = [ "ordered-stream", "serde", "serde_repr", + "tokio", "tracing", "uds_windows", "uuid", @@ -6093,6 +6303,7 @@ dependencies = [ "endi", "enumflags2", "serde", + "url", "winnow 0.7.13", "zvariant_derive", "zvariant_utils", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index aa81f8c..5651aef 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,6 +17,7 @@ tauri-build = { version = "2", features = [] } tauri = { version = "2", features = ["tray-icon"] } tauri-plugin-shell = "2" tauri-plugin-single-instance = "2" +tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" dirs = "6" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index aeaba5b..12718c1 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -3,5 +3,5 @@ "identifier": "default", "description": "Default permissions for the application", "windows": ["main"], - "permissions": ["core:default", "shell:allow-open"] + "permissions": ["core:default", "shell:allow-open", "dialog:allow-open"] } diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index 86cdb1f..35f90a7 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"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-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-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-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-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-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-internal-toggle-maximize"]},"permissions":{"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-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-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-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 +{"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"]},"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-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-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-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-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-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-internal-toggle-maximize"]},"permissions":{"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-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-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-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},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"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 index 569ea0a..62f66a6 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"default":{"identifier":"default","description":"Default permissions for the application","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}} \ No newline at end of file +{"default":{"identifier":"default","description":"Default permissions for the application","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog: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 index f827fe1..fcf88e0 100644 --- a/src-tauri/gen/schemas/desktop-schema.json +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -2354,6 +2354,72 @@ "const": "core:window:deny-unminimize", "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save 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", diff --git a/src-tauri/gen/schemas/windows-schema.json b/src-tauri/gen/schemas/windows-schema.json index f827fe1..fcf88e0 100644 --- a/src-tauri/gen/schemas/windows-schema.json +++ b/src-tauri/gen/schemas/windows-schema.json @@ -2354,6 +2354,72 @@ "const": "core:window:deny-unminimize", "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save 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", diff --git a/src-tauri/src/commands/tool_commands.rs b/src-tauri/src/commands/tool_commands.rs index 7e71399..804cd75 100644 --- a/src-tauri/src/commands/tool_commands.rs +++ b/src-tauri/src/commands/tool_commands.rs @@ -28,23 +28,21 @@ pub async fn check_installations( .map_err(|e| format!("检查工具状态失败: {}", e)) } -/// 刷新工具状态(重新检测并更新数据库) +/// 刷新工具状态(仅从数据库读取,不重新检测) /// -/// 工作流程: -/// 1. 重新检测所有本地工具(并行,约 1.3s) -/// 2. 更新数据库(upsert,删除已卸载的工具) -/// 3. 返回最新的 ToolStatus -/// -/// 用途:用户点击"刷新"按钮、安装/卸载工具后 +/// 修改说明:不再自动检测所有工具,仅返回数据库中已有的工具状态 +/// 如需添加新工具或验证已有工具,请使用: +/// - 添加新工具:工具管理页面 → 添加实例 +/// - 验证单个工具:使用 detect_single_tool 命令 #[tauri::command] pub async fn refresh_tool_status( registry_state: tauri::State<'_, ToolRegistryState>, ) -> Result, String> { let registry = registry_state.registry.lock().await; registry - .refresh_and_get_local_status() + .get_local_tool_status() .await - .map_err(|e| format!("刷新工具状态失败: {}", e)) + .map_err(|e| format!("获取工具状态失败: {}", e)) } /// 检测 Node.js 和 npm 环境 @@ -300,3 +298,235 @@ pub async fn update_tool(tool: String, force: Option) -> Result Result { + use std::path::PathBuf; + use std::process::Command; + + let path_buf = PathBuf::from(&path); + + // 检查文件是否存在 + if !path_buf.exists() { + return Err(format!("路径不存在: {}", path)); + } + + // 检查是否是文件 + if !path_buf.is_file() { + return Err(format!("路径不是文件: {}", path)); + } + + // 执行 --version 命令 + let version_cmd = format!("{} --version", path); + + #[cfg(target_os = "windows")] + let output = Command::new("cmd") + .arg("/C") + .arg(&version_cmd) + .output() + .map_err(|e| format!("执行命令失败: {}", e))?; + + #[cfg(not(target_os = "windows"))] + let output = Command::new("sh") + .arg("-c") + .arg(&version_cmd) + .output() + .map_err(|e| format!("执行命令失败: {}", e))?; + + if !output.status.success() { + return Err(format!("命令执行失败,退出码: {}", output.status)); + } + + // 解析版本号 + let version_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if version_str.is_empty() { + return Err("无法获取版本信息".to_string()); + } + + // 简单验证:版本号应该包含数字 + if !version_str.chars().any(|c| c.is_numeric()) { + return Err(format!("无效的版本信息: {}", version_str)); + } + + Ok(version_str) +} + +/// 手动添加工具实例(保存用户指定的路径) +/// +/// 工作流程: +/// 1. 验证路径有效性 +/// 2. 创建 ToolInstance +/// 3. 保存到数据库 +/// +/// 返回:工具状态信息 +#[tauri::command] +pub async fn add_manual_tool_instance( + tool_id: String, + path: String, + _registry_state: tauri::State<'_, ToolRegistryState>, +) -> Result { + use ::duckcoding::models::{InstallMethod, ToolInstance, ToolType}; + use ::duckcoding::services::tool::ToolInstanceDB; + + // 1. 验证路径 + let version = validate_tool_path(tool_id.clone(), path.clone()).await?; + + // 2. 创建工具显示名称 + let tool_name = match tool_id.as_str() { + "claude-code" => "Claude Code", + "codex" => "CodeX", + "gemini-cli" => "Gemini CLI", + _ => &tool_id, + }; + + // 3. 创建 ToolInstance + let instance_id = format!("{}-local-manual", tool_id); + let now = chrono::Utc::now().timestamp(); + let instance = ToolInstance { + instance_id: instance_id.clone(), + base_id: tool_id.clone(), + tool_name: tool_name.to_string(), + tool_type: ToolType::Local, + install_method: Some(InstallMethod::Npm), // 手动添加默认为 Npm,可后续优化 + installed: true, + version: Some(version.clone()), + install_path: Some(path.clone()), + wsl_distro: None, + ssh_config: None, + is_builtin: false, // 手动添加的工具不是内置的 + created_at: now, + updated_at: now, + }; + + // 4. 保存到数据库 + let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; + + db.add_instance(&instance) + .map_err(|e| format!("保存到数据库失败: {}", e))?; + + // 5. 返回 ToolStatus 格式 + Ok(crate::commands::types::ToolStatus { + id: tool_id.clone(), + name: tool_name.to_string(), + installed: true, + version: Some(version), + }) +} + +/// 检测单个工具但不保存(仅用于预览) +/// +/// 工作流程: +/// 1. 简化版检测:直接调用命令检查工具是否存在 +/// 2. 返回检测结果(不保存到数据库) +/// +/// 返回:工具状态信息 +#[tauri::command] +pub async fn detect_tool_without_save( + tool_id: String, + _registry_state: tauri::State<'_, ToolRegistryState>, +) -> Result { + use ::duckcoding::utils::CommandExecutor; + + let command_executor = CommandExecutor::new(); + + // 根据工具ID确定检测命令和名称 + let (check_cmd, tool_name) = match tool_id.as_str() { + "claude-code" => ("claude", "Claude Code"), + "codex" => ("codex", "CodeX"), + "gemini-cli" => ("gemini", "Gemini CLI"), + _ => return Err(format!("未知工具ID: {}", tool_id)), + }; + + // 检测工具是否存在 + let installed = command_executor.command_exists_async(check_cmd).await; + + let version = if installed { + // 获取版本 + let version_cmd = format!("{} --version", check_cmd); + let result = command_executor.execute_async(&version_cmd).await; + if result.success { + let version_str = result.stdout.trim().to_string(); + if !version_str.is_empty() { + Some(version_str) + } else { + None + } + } else { + None + } + } else { + None + }; + + Ok(crate::commands::types::ToolStatus { + id: tool_id.clone(), + name: tool_name.to_string(), + installed, + version, + }) +} + +/// 检测单个工具并保存到数据库 +/// +/// 工作流程: +/// 1. 先查询数据库中是否已有该工具的实例 +/// 2. 如果已有且已安装,直接返回(除非 force_redetect = true) +/// 3. 如果没有或需要重新检测,执行单工具检测(会先删除旧实例) +/// +/// 返回:工具实例信息 +#[tauri::command] +pub async fn detect_single_tool( + tool_id: String, + force_redetect: Option, + registry_state: tauri::State<'_, ToolRegistryState>, +) -> Result { + use ::duckcoding::models::ToolType; + use ::duckcoding::services::tool::ToolInstanceDB; + + let force = force_redetect.unwrap_or(false); + + if !force { + // 1. 先查询数据库中是否已有该工具的本地实例 + let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; + let all_instances = db + .get_all_instances() + .map_err(|e| format!("读取数据库失败: {}", e))?; + + // 查找该工具的本地实例 + if let Some(existing) = all_instances.iter().find(|inst| { + inst.base_id == tool_id && inst.tool_type == ToolType::Local && inst.installed + }) { + // 如果已有实例且已安装,直接返回 + tracing::info!("工具 {} 已在数据库中,直接返回", existing.tool_name); + return Ok(crate::commands::types::ToolStatus { + id: tool_id.clone(), + name: existing.tool_name.clone(), + installed: true, + version: existing.version.clone(), + }); + } + } + + // 2. 执行单工具检测(会删除旧实例避免重复) + let registry = registry_state.registry.lock().await; + let instance = registry + .detect_and_persist_single_tool(&tool_id) + .await + .map_err(|e| format!("检测失败: {}", e))?; + + // 3. 返回 ToolStatus 格式 + Ok(crate::commands::types::ToolStatus { + id: tool_id.clone(), + name: instance.tool_name.clone(), + installed: instance.installed, + version: instance.version.clone(), + }) +} diff --git a/src-tauri/src/commands/tool_management.rs b/src-tauri/src/commands/tool_management.rs index d459bdb..a0f2b5b 100644 --- a/src-tauri/src/commands/tool_management.rs +++ b/src-tauri/src/commands/tool_management.rs @@ -22,16 +22,14 @@ pub async fn has_tools_in_database( .map_err(|e| format!("检查数据库失败: {}", e)) } -/// 检测本地工具并保存到数据库(用于新手引导) +/// 检测本地工具并保存到数据库(已废弃) +/// +/// 此命令已废弃,请使用工具管理页面的「添加实例」功能单独添加工具 #[tauri::command] pub async fn detect_and_save_tools( - state: tauri::State<'_, ToolRegistryState>, + _state: tauri::State<'_, ToolRegistryState>, ) -> Result, String> { - let registry = state.registry.lock().await; - registry - .detect_and_persist_local_tools() - .await - .map_err(|e| format!("检测工具失败: {}", e)) + Err("此功能已废弃,请使用工具管理页面的「添加实例」功能单独添加工具".to_string()) } /// 获取所有工具实例(按工具ID分组)- 只从数据库读取 @@ -46,16 +44,19 @@ pub async fn get_tool_instances( .map_err(|e| format!("获取工具实例失败: {}", e)) } -/// 刷新工具实例状态(重新检测并更新数据库) +/// 刷新工具实例状态(仅从数据库读取,不重新检测) +/// +/// 修改说明:不再自动检测所有工具,仅返回数据库中已有的工具实例 +/// 如需添加新工具,请使用工具管理页面的「添加实例」功能 #[tauri::command] pub async fn refresh_tool_instances( state: tauri::State<'_, ToolRegistryState>, ) -> Result>, String> { let registry = state.registry.lock().await; registry - .refresh_all() + .get_all_grouped() .await - .map_err(|e| format!("刷新工具实例失败: {}", e)) + .map_err(|e| format!("获取工具实例失败: {}", e)) } /// 列出所有可用的WSL发行版 diff --git a/src-tauri/src/commands/types.rs b/src-tauri/src/commands/types.rs index ac3f180..9ad71a7 100644 --- a/src-tauri/src/commands/types.rs +++ b/src-tauri/src/commands/types.rs @@ -3,6 +3,16 @@ // 重新导出 ToolStatus(定义在 models 层) pub use duckcoding::models::ToolStatus; +/// 工具扫描结果(用于添加实例预览,包含路径信息) +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ToolScanResult { + pub id: String, + pub name: String, + pub installed: bool, + pub version: Option, + pub install_path: Option, +} + /// Node 环境信息 #[derive(serde::Serialize, serde::Deserialize)] pub struct NodeEnvironment { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bd2c709..42bc042 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -413,7 +413,8 @@ fn main() { Ok(()) }) - .plugin(tauri_plugin_shell::init()); + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()); // 条件注册单实例插件 let builder = if single_instance_enabled { @@ -450,6 +451,10 @@ fn main() { check_update, check_all_updates, update_tool, + validate_tool_path, + add_manual_tool_instance, + detect_single_tool, + detect_tool_without_save, // 请使用新的 pm_ 系列命令 save_global_config, get_global_config, diff --git a/src-tauri/src/services/tool/registry.rs b/src-tauri/src/services/tool/registry.rs index 8b38f09..c8e1aed 100644 --- a/src-tauri/src/services/tool/registry.rs +++ b/src-tauri/src/services/tool/registry.rs @@ -150,26 +150,76 @@ impl ToolRegistry { // 创建 ToolInstance(需要获取 Tool 的完整信息) let tool = Tool::by_id(tool_id).unwrap_or_else(|| { - // 如果 Tool 定义不存在,从 Detector 构建最小化 Tool - Tool { - id: tool_id.to_string(), - name: tool_name.to_string(), - group_name: format!("{} 专用分组", tool_name), - npm_package: detector.npm_package().to_string(), - check_command: detector.check_command().to_string(), - config_dir: detector.config_dir(), - config_file: detector.config_file().to_string(), - env_vars: crate::models::EnvVars { - api_key: String::new(), - base_url: String::new(), - }, - use_proxy_for_version_check: detector.use_proxy_for_version_check(), + tracing::warn!("未找到工具定义: {}, 使用对应的静态方法", tool_id); + match tool_id { + "claude-code" => Tool::claude_code(), + "codex" => Tool::codex(), + "gemini-cli" => Tool::gemini_cli(), + _ => panic!("未知工具ID: {}", tool_id), } }); - let mut instance = ToolInstance::from_tool_local(&tool, installed, version, install_path); - instance.install_method = install_method; // 设置检测到的安装方式 - instance + let instance_id = format!("{}-local", tool_id); + let now = chrono::Utc::now().timestamp(); + + ToolInstance { + instance_id, + base_id: tool.id.clone(), + tool_name: tool.name.clone(), + tool_type: ToolType::Local, + install_method, + installed, + version, + install_path, + wsl_distro: None, + ssh_config: None, + is_builtin: true, + created_at: now, + updated_at: now, + } + } + + /// 检测单个本地工具并持久化(公开方法) + /// + /// 工作流程: + /// 1. 删除该工具的所有现有本地实例(避免重复) + /// 2. 执行检测 + /// 3. 如果检测到,保存到数据库 + /// + /// 返回:工具实例 + pub async fn detect_and_persist_single_tool(&self, tool_id: &str) -> Result { + let detector = self + .detector_registry + .get(tool_id) + .ok_or_else(|| anyhow::anyhow!("未找到工具 {} 的检测器", tool_id))?; + + tracing::info!("开始检测单个工具: {}", tool_id); + + // 1. 删除该工具的所有本地实例(避免重复) + let db = self.db.lock().await; + let all_instances = db.get_all_instances()?; + for inst in all_instances { + if inst.base_id == tool_id && inst.tool_type == ToolType::Local { + tracing::info!("删除旧实例: {}", inst.instance_id); + let _ = db.delete_instance(&inst.instance_id); + } + } + drop(db); + + // 2. 执行检测 + let instance = self.detect_single_tool_by_detector(detector).await; + + // 3. 保存到数据库 + let db = self.db.lock().await; + if instance.installed { + db.upsert_instance(&instance)?; + tracing::info!("工具 {} 检测并保存成功", instance.tool_name); + } else { + tracing::info!("工具 {} 未检测到", instance.tool_name); + } + drop(db); + + Ok(instance) } /// 刷新本地工具状态(重新检测,更新存在的,删除不存在的) @@ -357,16 +407,7 @@ impl ToolRegistry { pub async fn get_local_tool_status(&self) -> Result> { tracing::debug!("获取本地工具轻量级状态"); - // 检查数据库是否有本地工具数据 - let has_data = self.has_local_tools_in_db().await?; - - if !has_data { - tracing::info!("数据库为空,执行首次检测并持久化"); - // 首次检测并保存到数据库 - self.detect_and_persist_local_tools().await?; - } - - // 从数据库读取所有实例 + // 从数据库读取所有实例(不主动检测) let grouped = self.get_all_grouped().await?; // 转换为轻量级 ToolStatus diff --git a/src/App.tsx b/src/App.tsx index 04a8cd9..494a05d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { listen } from '@tauri-apps/api/event'; +import { listen, emit } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core'; import { AppSidebar } from '@/components/layout/AppSidebar'; import { CloseActionDialog } from '@/components/dialogs/CloseActionDialog'; @@ -55,6 +55,7 @@ function App() { const [selectedProxyToolId, setSelectedProxyToolId] = useState(undefined); const [settingsInitialTab, setSettingsInitialTab] = useState('basic'); const [settingsRestrictToTab, setSettingsRestrictToTab] = useState(undefined); + const [restrictedPage, setRestrictedPage] = useState(undefined); // 引导状态管理 const [showOnboarding, setShowOnboarding] = useState(false); @@ -283,11 +284,51 @@ function App() { }, ); + // 监听统一引导导航事件(标准化) + const unlistenOnboardingNavigate = listen<{ + targetPage: string; + restrictToTab?: string; + autoAction?: string; + }>('onboarding-navigate', (event) => { + const { targetPage, restrictToTab, autoAction } = event.payload || {}; + + console.log('[Onboarding Nav] 接收导航事件:', { targetPage, restrictToTab, autoAction }); + + // 设置页面限制 + setRestrictedPage(targetPage); + + // 处理设置页面的特殊逻辑 + if (targetPage === 'settings' && restrictToTab) { + setSettingsInitialTab(restrictToTab); + setSettingsRestrictToTab(restrictToTab); + } + + // 跳转到目标页面 + setActiveTab(targetPage as TabType); + + // 延迟触发自动动作(等待页面渲染和事件监听建立) + if (autoAction) { + console.log('[Onboarding Nav] 将在 500ms 后触发自动动作:', autoAction); + setTimeout(() => { + console.log('[Onboarding Nav] 触发自动动作:', autoAction); + emit(autoAction); + }, 500); + } + }); + + // 监听清除引导限制 + const unlistenClearRestriction = listen('clear-onboarding-restriction', () => { + setRestrictedPage(undefined); + setSettingsRestrictToTab(undefined); + }); + return () => { unlistenUpdateAvailable.then((fn) => fn()); unlistenRequestCheck.then((fn) => fn()); unlistenNotFound.then((fn) => fn()); unlistenOpenSettings.then((fn) => fn()); + unlistenOnboardingNavigate.then((fn) => fn()); + unlistenClearRestriction.then((fn) => fn()); }; }, [toast]); @@ -371,14 +412,19 @@ function App() { setActiveTab(tab as TabType)} - restrictNavigation={!!settingsRestrictToTab} + restrictNavigation={!!settingsRestrictToTab || !!restrictedPage} + allowedPage={restrictedPage || (settingsRestrictToTab ? 'settings' : undefined)} /> {/* 主内容区域 */}
{activeTab === 'dashboard' && } {activeTab === 'tool-management' && ( - + )} {activeTab === 'install' && } {activeTab === 'balance' && } diff --git a/src/components/Onboarding/steps/v3/ToolDetectionStep.tsx b/src/components/Onboarding/steps/v3/ToolDetectionStep.tsx index 58bdc37..d7eacba 100644 --- a/src/components/Onboarding/steps/v3/ToolDetectionStep.tsx +++ b/src/components/Onboarding/steps/v3/ToolDetectionStep.tsx @@ -1,190 +1,104 @@ // filepath: e:\DuckCoding\src\components\Onboarding\steps\v3\ToolDetectionStep.tsx -import { useState, useEffect, useRef } from 'react'; +import { useEffect } from 'react'; import { StepProps } from '../../../../types/onboarding'; -import { detectAndSaveTools } from '@/lib/tauri-commands'; -import type { ToolInstance } from '@/types/tool-management'; - -// 工具信息定义 -const TOOLS = [ - { id: 'claude-code', name: 'Claude Code', icon: '🤖' }, - { id: 'codex', name: 'CodeX', icon: '📦' }, - { id: 'gemini-cli', name: 'Gemini CLI', icon: '✨' }, -]; - -type DetectionStatus = 'pending' | 'detecting' | 'done' | 'error'; - -interface ToolDetectionState { - status: DetectionStatus; - installed: boolean; - version: string | null; -} - -export default function ToolDetectionStep({ onNext }: StepProps) { - const [detecting, setDetecting] = useState(false); - const [toolStates, setToolStates] = useState>(() => { - const initial: Record = {}; - TOOLS.forEach((tool) => { - initial[tool.id] = { status: 'pending', installed: false, version: null }; - }); - return initial; - }); - const [error, setError] = useState(null); - const [completed, setCompleted] = useState(false); - - // 使用 ref 追踪是否已开始检测,防止重复执行 - const hasStartedRef = useRef(false); - - const runDetection = async () => { - if (detecting) return; - - setDetecting(true); - setError(null); - - // 设置所有工具为检测中状态 - setToolStates((prev) => { - const updated = { ...prev }; - TOOLS.forEach((tool) => { - updated[tool.id] = { ...updated[tool.id], status: 'detecting' }; - }); - return updated; - }); +import type { OnboardingNavigationPayload } from '../../../../types/onboarding'; +import { emit, listen } from '@tauri-apps/api/event'; +export default function ToolDetectionStep({ onNext, onPrevious, isFirst }: StepProps) { + const handleGoToToolManagement = async () => { try { - // 调用后端并行检测 - const results = await detectAndSaveTools(); - - // 更新各工具状态 - setToolStates((prev) => { - const updated = { ...prev }; - results.forEach((instance: ToolInstance) => { - if (updated[instance.base_id]) { - updated[instance.base_id] = { - status: 'done', - installed: instance.installed, - version: instance.version ?? null, - }; - } - }); - // 确保没有结果的工具也标记为完成 - TOOLS.forEach((tool) => { - if (updated[tool.id].status !== 'done') { - updated[tool.id] = { status: 'done', installed: false, version: null }; - } - }); - return updated; - }); - - setCompleted(true); - } catch (err) { - setError(err instanceof Error ? err.message : '检测失败'); - setToolStates((prev) => { - const updated = { ...prev }; - TOOLS.forEach((tool) => { - if (updated[tool.id].status === 'detecting') { - updated[tool.id] = { ...updated[tool.id], status: 'error' }; - } - }); - return updated; - }); - } finally { - setDetecting(false); + // 隐藏引导界面 + await emit('hide-onboarding'); + // 使用标准化导航事件 + await emit('onboarding-navigate', { + targetPage: 'tool-management', + autoAction: 'open-add-instance-dialog', + } as OnboardingNavigationPayload); + } catch (error) { + console.error('打开工具管理页面失败:', error); } }; - // 组件挂载时自动开始检测(仅执行一次) + const handleNext = async () => { + // 清除限制并恢复引导界面 + await emit('clear-onboarding-restriction'); + await emit('show-onboarding'); + onNext(); + }; + + // 监听继续引导事件 useEffect(() => { - if (hasStartedRef.current) return; - hasStartedRef.current = true; - runDetection(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const unlisten = listen('continue-onboarding', async () => { + await emit('clear-onboarding-restriction'); + await emit('show-onboarding'); + onNext(); + }); - const installedCount = Object.values(toolStates).filter( - (s) => s.status === 'done' && s.installed, - ).length; + return () => { + unlisten.then((fn) => fn()); + }; + }, [onNext]); return (
- 🔍 + 🛠️
-

检测系统工具

- -

正在检测您系统中已安装的 AI 编程工具...

- -
- {TOOLS.map((tool) => { - const state = toolStates[tool.id]; - return ( -
-
{tool.icon}
-
-
{tool.name}
-
- {state.status === 'pending' && 等待检测} - {state.status === 'detecting' && ( - - 检测中... - - )} - {state.status === 'done' && state.installed && ( - - 已安装 {state.version && v{state.version}} - - )} - {state.status === 'done' && !state.installed && ( - 未安装 - )} - {state.status === 'error' && 检测失败} -
-
-
- {state.status === 'done' && state.installed && ( - - )} - {state.status === 'done' && !state.installed && ( - - )} -
-
- ); - })} -
- - {error && ( -
-

{error}

- +

配置工具实例

+ +

+ DuckCoding 支持管理多个 AI 编程工具实例,您可以在工具管理页面添加和配置它们 +

+ +
+
💡
+
+

如何添加工具实例?

+

点击下方的「前往配置」按钮,在工具管理页面点击「添加实例」,然后选择:

+
    +
  • + 本地环境:支持自动扫描或手动指定路径 +
  • +
  • + WSL 环境:在 Windows 子系统 Linux 中使用工具 +
  • +
- )} +
- {completed && ( -
- {installedCount > 0 ? ( -

- 检测到 {installedCount} 个已安装的工具 -

- ) : ( -

未检测到已安装的工具,您可以稍后在工具管理页面安装

- )} -
- )} +
+

支持的添加方式

+
    +
  • + 自动扫描:自动检测系统中已安装的工具(npm、Homebrew 等) +
  • +
  • + 手动指定:选择工具可执行文件路径 +
  • +
+
- + +
+ + +
+ +

+ 提示:点击「前往配置」会打开工具管理页面,配置完成后点击「下一步」继续引导 +

); diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx index f75875d..0ef918b 100644 --- a/src/components/layout/AppSidebar.tsx +++ b/src/components/layout/AppSidebar.tsx @@ -37,6 +37,7 @@ interface AppSidebarProps { activeTab: string; onTabChange: (tab: string) => void; restrictNavigation?: boolean; + allowedPage?: string; // 新增:允许访问的页面(引导模式下) } // 导航项配置 @@ -54,7 +55,12 @@ const secondaryItems = [ { id: 'settings', label: '设置', icon: SettingsIcon }, ]; -export function AppSidebar({ activeTab, onTabChange, restrictNavigation }: AppSidebarProps) { +export function AppSidebar({ + activeTab, + onTabChange, + restrictNavigation, + allowedPage, +}: AppSidebarProps) { const { toast } = useToast(); const { theme, actualTheme, setTheme } = useTheme(); @@ -68,13 +74,16 @@ export function AppSidebar({ activeTab, onTabChange, restrictNavigation }: AppSi }, [isCollapsed]); const handleTabChange = (tab: string) => { - if (restrictNavigation && tab !== activeTab) { - toast({ - title: '请先完成引导', - description: '完成当前引导步骤后即可访问其他页面', - variant: 'default', - }); - return; + if (restrictNavigation) { + // 只允许访问 allowedPage + if (allowedPage && tab !== allowedPage) { + toast({ + title: '请先完成引导', + description: '完成当前引导步骤后即可访问其他页面', + variant: 'default', + }); + return; + } } onTabChange(tab); }; @@ -96,7 +105,7 @@ export function AppSidebar({ activeTab, onTabChange, restrictNavigation }: AppSi : 'hover:bg-accent hover:shadow-md hover:scale-[1.02]' }`} onClick={() => handleTabChange(item.id)} - disabled={restrictNavigation && activeTab !== item.id} + disabled={restrictNavigation && allowedPage ? item.id !== allowedPage : false} > {isActive && !isCollapsed && (
diff --git a/src/lib/tauri-commands.ts b/src/lib/tauri-commands.ts index e5d5f86..dc3bffd 100644 --- a/src/lib/tauri-commands.ts +++ b/src/lib/tauri-commands.ts @@ -868,3 +868,45 @@ export async function updateProxyConfig(toolId: ToolId, config: ToolProxyConfig) export async function getAllProxyConfigs(): Promise> { return invoke>('get_all_proxy_configs'); } + +/** + * 验证用户指定的工具路径是否有效 + * @param toolId 工具ID + * @param path 工具可执行文件路径 + * @returns 版本号字符串 + */ +export async function validateToolPath(toolId: string, path: string): Promise { + return invoke('validate_tool_path', { toolId, path }); +} + +/** + * 手动添加工具实例(保存用户指定的路径) + * @param toolId 工具ID + * @param path 工具可执行文件路径 + * @returns 工具状态信息 + */ +export async function addManualToolInstance(toolId: string, path: string): Promise { + return invoke('add_manual_tool_instance', { toolId, path }); +} + +/** + * 检测单个工具但不保存(仅用于预览) + * @param toolId 工具ID + * @returns 工具状态信息 + */ +export async function detectToolWithoutSave(toolId: string): Promise { + return invoke('detect_tool_without_save', { toolId }); +} + +/** + * 检测单个工具并保存到数据库 + * @param toolId 工具ID + * @param forceRedetect 是否强制重新检测(默认 false,会优先读取数据库) + * @returns 工具状态信息 + */ +export async function detectSingleTool( + toolId: string, + forceRedetect?: boolean, +): Promise { + return invoke('detect_single_tool', { toolId, forceRedetect }); +} diff --git a/src/pages/Onboarding/components/ManualPathDialog.tsx b/src/pages/Onboarding/components/ManualPathDialog.tsx new file mode 100644 index 0000000..2e14f87 --- /dev/null +++ b/src/pages/Onboarding/components/ManualPathDialog.tsx @@ -0,0 +1,219 @@ +import { useState } from 'react'; +import { open } from '@tauri-apps/plugin-dialog'; +import { + Dialog, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from '@/components/ui/dialog'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X, InfoIcon, Loader2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { validateToolPath } from '@/lib/tauri-commands'; +import { useToast } from '@/hooks/use-toast'; +import { cn } from '@/lib/utils'; + +interface ManualPathDialogProps { + toolId: string; + toolName: string; + onConfirm: (toolId: string, path: string) => Promise; + onCancel: () => void; +} + +export function ManualPathDialog({ toolId, toolName, onConfirm, onCancel }: ManualPathDialogProps) { + const [path, setPath] = useState(''); + const [validating, setValidating] = useState(false); + const [validationError, setValidationError] = useState(null); + const [confirming, setConfirming] = useState(false); + const { toast } = useToast(); + + // 获取常见安装路径示例 + const getCommonPaths = () => { + const isWindows = navigator.platform.toLowerCase().includes('win'); + if (isWindows) { + return [ + `C:\\Users\\用户名\\AppData\\Roaming\\npm\\${toolId}.cmd`, + `C:\\Users\\用户名\\.npm-global\\${toolId}.cmd`, + `C:\\Program Files\\${toolName}\\${toolId}.exe`, + ]; + } else { + return [ + `~/.npm-global/bin/${toolId}`, + `/usr/local/bin/${toolId}`, + `/opt/homebrew/bin/${toolId}`, + `~/.local/bin/${toolId}`, + ]; + } + }; + + // 打开文件选择器 + const handleBrowse = async () => { + try { + const isWindows = navigator.platform.toLowerCase().includes('win'); + const selected = await open({ + directory: false, + multiple: false, + title: `选择 ${toolName} 可执行文件`, + filters: [ + { + name: '可执行文件', + extensions: isWindows ? ['exe', 'cmd', 'bat'] : [], + }, + ], + }); + + if (selected && typeof selected === 'string') { + setPath(selected); + handleValidate(selected); + } + } catch (error) { + toast({ + variant: 'destructive', + title: '打开文件选择器失败', + description: String(error), + }); + } + }; + + // 验证路径 + const handleValidate = async (pathToValidate: string) => { + if (!pathToValidate.trim()) { + setValidationError('请输入路径'); + return; + } + + setValidating(true); + setValidationError(null); + + try { + await validateToolPath(toolId, pathToValidate); + // 验证成功,清除错误 + } catch (error) { + setValidationError(String(error)); + } finally { + setValidating(false); + } + }; + + // 确认保存 + const handleConfirm = async () => { + if (!path || validating || validationError) return; + + setConfirming(true); + try { + await onConfirm(toolId, path); + } catch (error) { + toast({ + variant: 'destructive', + title: '保存路径失败', + description: String(error), + }); + } finally { + setConfirming(false); + } + }; + + return ( + !open && onCancel()}> + + {/* 自定义 Overlay,z-index 高于 Onboarding (10000) */} + + {/* 自定义 Content,z-index 高于 Overlay */} + + {/* 关闭按钮 */} + + + Close + + + + 手动指定 {toolName} 路径 + 自动扫描未找到该工具,请选择可执行文件路径 + + +
+ {/* 常见路径提示 */} + + + 常见安装路径 + +
    + {getCommonPaths().map((examplePath, index) => ( +
  • + {examplePath} +
  • + ))} +
+
+
+ + {/* 路径输入 */} +
+ { + setPath(e.target.value); + setValidationError(null); + }} + onBlur={() => { + if (path) handleValidate(path); + }} + placeholder="输入或浏览选择可执行文件" + disabled={validating || confirming} + /> + +
+ + {/* 验证状态 */} + {validating && ( +
+ + 验证中... +
+ )} + + {validationError && ( + + {validationError} + + )} +
+ + + + + +
+
+
+ ); +} diff --git a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx index 0ce8f6d..18995a5 100644 --- a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx +++ b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx @@ -7,6 +7,7 @@ import { } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; import { Select, SelectContent, @@ -14,39 +15,79 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Alert, AlertDescription } from '@/components/ui/alert'; import { useState, useEffect, useCallback } from 'react'; +import { Loader2, InfoIcon, CheckCircle2 } from 'lucide-react'; +import { open as openDialog } from '@tauri-apps/plugin-dialog'; import type { SSHConfig } from '@/types/tool-management'; -import { listWslDistributions } from '@/lib/tauri-commands'; +import { + listWslDistributions, + detectSingleTool, + detectToolWithoutSave, + validateToolPath, + addManualToolInstance, +} from '@/lib/tauri-commands'; import { useToast } from '@/hooks/use-toast'; +import { cn } from '@/lib/utils'; interface AddInstanceDialogProps { open: boolean; onClose: () => void; onAdd: ( baseId: string, - type: 'wsl' | 'ssh', + type: 'local' | 'wsl' | 'ssh', sshConfig?: SSHConfig, distroName?: string, ) => Promise; } +const TOOLS = [ + { id: 'claude-code', name: 'Claude Code' }, + { id: 'codex', name: 'CodeX' }, + { id: 'gemini-cli', name: 'Gemini CLI' }, +]; + +const ENV_TYPES = [ + { id: 'local', name: '本地环境', description: '在本机直接运行工具' }, + { id: 'wsl', name: 'WSL 环境', description: 'Windows子系统Linux环境', disabled: true }, + { id: 'ssh', name: 'SSH 远程', description: '远程服务器环境(开发中)', disabled: true }, +]; + +const LOCAL_METHODS = [ + { id: 'auto', name: '自动扫描', description: '自动检测系统中已安装的工具' }, + { id: 'manual', name: '手动指定', description: '选择工具可执行文件路径' }, +]; + export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogProps) { const { toast } = useToast(); const [baseId, setBaseId] = useState('claude-code'); - const [envType, setEnvType] = useState<'wsl' | 'ssh'>('wsl'); + const [envType, setEnvType] = useState<'local' | 'wsl' | 'ssh'>('local'); + const [localMethod, setLocalMethod] = useState<'auto' | 'manual'>('auto'); + const [manualPath, setManualPath] = useState(''); + const [validating, setValidating] = useState(false); + const [validationError, setValidationError] = useState(null); + const [scanning, setScanning] = useState(false); + const [scanResult, setScanResult] = useState<{ installed: boolean; version: string } | null>( + null, + ); const [loading, setLoading] = useState(false); const [wslDistros, setWslDistros] = useState([]); const [selectedDistro, setSelectedDistro] = useState(''); const [loadingDistros, setLoadingDistros] = useState(false); + const toolNames: Record = { + 'claude-code': 'Claude Code', + codex: 'CodeX', + 'gemini-cli': 'Gemini CLI', + }; + const loadWslDistros = useCallback(async () => { setLoadingDistros(true); try { const distros = await listWslDistributions(); setWslDistros(distros); if (distros.length > 0) { - setSelectedDistro(distros[0]); // 默认选择第一个 + setSelectedDistro(distros[0]); } } catch (err) { toast({ @@ -60,95 +101,460 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro } }, [toast]); - // 当选择WSL时,加载发行版列表 useEffect(() => { if (open && envType === 'wsl') { loadWslDistros(); } }, [open, envType, loadWslDistros]); - const handleSubmit = async () => { - if (envType === 'ssh') { - // SSH功能暂不实现 - return; + // 重置扫描结果:当用户更改工具、环境类型或添加方式时 + useEffect(() => { + setScanResult(null); + }, [baseId, envType, localMethod]); + + const getCommonPaths = () => { + const isWindows = navigator.platform.toLowerCase().includes('win'); + if (isWindows) { + return [ + `C:\\Users\\用户名\\AppData\\Roaming\\npm\\${baseId}.cmd`, + `C:\\Users\\用户名\\.npm-global\\${baseId}.cmd`, + `C:\\Program Files\\${toolNames[baseId]}\\${baseId}.exe`, + ]; + } else { + return [ + `~/.npm-global/bin/${baseId}`, + `/usr/local/bin/${baseId}`, + `/opt/homebrew/bin/${baseId}`, + `~/.local/bin/${baseId}`, + ]; } + }; + + const handleBrowse = async () => { + try { + const isWindows = navigator.platform.toLowerCase().includes('win'); + const selected = await openDialog({ + directory: false, + multiple: false, + title: `选择 ${toolNames[baseId]} 可执行文件`, + filters: [ + { + name: '可执行文件', + extensions: isWindows ? ['exe', 'cmd', 'bat'] : [], + }, + ], + }); - if (envType === 'wsl' && !selectedDistro) { + if (selected && typeof selected === 'string') { + setManualPath(selected); + handleValidate(selected); + } + } catch (error) { toast({ - title: '请选择WSL发行版', variant: 'destructive', + title: '打开文件选择器失败', + description: String(error), }); + } + }; + + const handleValidate = async (pathToValidate: string) => { + if (!pathToValidate.trim()) { + setValidationError('请输入路径'); return; } - setLoading(true); + setValidating(true); + setValidationError(null); + try { - await onAdd(baseId, envType, undefined, selectedDistro); - onClose(); - // 重置状态 - setBaseId('claude-code'); - setEnvType('wsl'); - setSelectedDistro(''); + await validateToolPath(baseId, pathToValidate); + } catch (error) { + setValidationError(String(error)); + } finally { + setValidating(false); + } + }; + + // 执行扫描/验证(不保存) + const handleScan = async () => { + if (envType !== 'local') return; + + console.log('[AddInstance] 开始扫描,工具:', baseId, '方式:', localMethod); + setScanning(true); + setScanResult(null); + + try { + if (localMethod === 'auto') { + // 自动扫描(不保存到数据库) + console.log('[AddInstance] 调用 detectToolWithoutSave,工具:', baseId); + const result = await detectToolWithoutSave(baseId); + console.log('[AddInstance] 扫描结果:', result); + setScanResult({ installed: result.installed, version: result.version || '未知' }); + + if (!result.installed) { + toast({ + variant: 'destructive', + title: '未检测到工具', + description: `未在系统中检测到 ${toolNames[baseId]}`, + }); + } else { + toast({ + title: '检测成功', + description: `${toolNames[baseId]} v${result.version}`, + }); + } + } else { + // 手动验证路径(不保存) + if (!manualPath) { + toast({ + variant: 'destructive', + title: '请选择路径', + }); + return; + } + if (validationError) { + toast({ + variant: 'destructive', + title: '路径验证失败', + description: validationError, + }); + return; + } + + console.log('[AddInstance] 验证路径:', manualPath); + const version = await validateToolPath(baseId, manualPath); + console.log('[AddInstance] 验证结果:', version); + setScanResult({ installed: true, version }); + + toast({ + title: '验证成功', + description: `${toolNames[baseId]} v${version}`, + }); + } + } catch (error) { + console.error('[AddInstance] 扫描/验证失败:', error); + toast({ + variant: 'destructive', + title: '扫描失败', + description: String(error), + }); + setScanResult(null); } finally { - setLoading(false); + setScanning(false); + } + }; + + const handleSubmit = async () => { + if (envType === 'local') { + // 本地环境:保存已扫描的实例 + if (!scanResult || !scanResult.installed) { + toast({ + variant: 'destructive', + title: '无可用结果', + description: '请先执行扫描', + }); + return; + } + + setLoading(true); + try { + if (localMethod === 'auto') { + // 自动扫描:调用保存命令 + console.log('[AddInstance] 保存自动扫描结果,工具:', baseId); + const result = await detectSingleTool(baseId, true); + if (!result.installed) { + toast({ + variant: 'destructive', + title: '保存失败', + description: '工具状态已变化,请重新扫描', + }); + return; + } + toast({ + title: '添加成功', + description: `${toolNames[baseId]} v${result.version}`, + }); + } else { + // 手动指定:保存路径 + console.log('[AddInstance] 保存手动指定路径:', manualPath); + await addManualToolInstance(baseId, manualPath); + toast({ + title: '添加成功', + description: `${toolNames[baseId]} 已成功添加`, + }); + } + + await onAdd(baseId, 'local'); + handleClose(); + } catch (error) { + toast({ + variant: 'destructive', + title: '添加失败', + description: String(error), + }); + } finally { + setLoading(false); + } + } else if (envType === 'ssh') { + return; + } else if (envType === 'wsl') { + if (!selectedDistro) { + toast({ + title: '请选择WSL发行版', + variant: 'destructive', + }); + return; + } + + setLoading(true); + try { + await onAdd(baseId, envType, undefined, selectedDistro); + handleClose(); + } finally { + setLoading(false); + } } }; const handleClose = () => { - if (!loading) { + if (!loading && !scanning) { onClose(); - // 重置状态 setBaseId('claude-code'); - setEnvType('wsl'); + setEnvType('local'); + setLocalMethod('auto'); + setManualPath(''); + setValidationError(null); setSelectedDistro(''); + setScanResult(null); } }; return ( - - + !isOpen && !loading && onClose()} modal> + e.preventDefault()}> 添加工具实例 -
- {/* 选择工具 */} -
- - +
+ {/* 选择工具 - 卡片式 */} +
+ +
+ {TOOLS.map((tool) => ( + + ))} +
- {/* 选择环境类型 */} -
- - setEnvType(value as 'wsl' | 'ssh')} - > -
- - -
-
- - -
-
+ {/* 选择环境类型 - 卡片式 */} +
+ +
+ {ENV_TYPES.map((env) => ( + + ))} +
+ {/* 本地环境配置 */} + {envType === 'local' && ( + <> +
+ +
+ {LOCAL_METHODS.map((method) => ( + + ))} +
+
+ + {localMethod === 'auto' && ( + <> + + + + 将自动扫描系统中已安装的 {toolNames[baseId]},包括 npm、Homebrew 等安装方式 + + + +
+ + + {scanResult && ( + + + + {scanResult.installed ? ( + <> + ✓ 检测成功:{toolNames[baseId]} v{scanResult.version} + + ) : ( + <>未检测到工具,请尝试手动指定路径 + )} + + + )} +
+ + )} + + {localMethod === 'manual' && ( + <> + + + +

常见安装路径:

+
    + {getCommonPaths().map((path, index) => ( +
  • + {path} +
  • + ))} +
+
+
+ +
+ +
+ { + setManualPath(e.target.value); + setValidationError(null); + setScanResult(null); // 清除扫描结果 + }} + onBlur={() => { + if (manualPath) handleValidate(manualPath); + }} + placeholder="输入或浏览选择" + disabled={validating || loading || scanning} + /> + +
+ + {validating && ( +
+ + 验证中... +
+ )} + + {validationError && ( + + {validationError} + + )} + + + + {scanResult && ( + + + + {scanResult.installed ? ( + <> + ✓ 验证成功:{toolNames[baseId]} v{scanResult.version} + + ) : ( + <>验证失败 + )} + + + )} +
+ + )} + + )} + {/* WSL发行版选择 */} {envType === 'wsl' && (
@@ -194,14 +600,27 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro
- diff --git a/src/pages/ToolManagementPage/hooks/useToolManagement.ts b/src/pages/ToolManagementPage/hooks/useToolManagement.ts index 59e02c9..342678e 100644 --- a/src/pages/ToolManagementPage/hooks/useToolManagement.ts +++ b/src/pages/ToolManagementPage/hooks/useToolManagement.ts @@ -83,22 +83,33 @@ export function useToolManagement() { // 添加实例 const handleAddInstance = useCallback( - async (baseId: string, type: 'wsl' | 'ssh', sshConfig?: SSHConfig, distroName?: string) => { + async ( + baseId: string, + type: 'local' | 'wsl' | 'ssh', + sshConfig?: SSHConfig, + distroName?: string, + ) => { try { - if (type === 'wsl') { + if (type === 'local') { + // 本地实例已通过 detectSingleTool 或 addManualToolInstance 添加 + // 这里只需要重新读取数据库,不重新检测 + toast({ title: '添加成功', description: '本地工具实例已添加' }); + await loadTools(); // 只从数据库读取,不重新检测 + } else if (type === 'wsl') { if (!distroName) { throw new Error('WSL发行版名称不能为空'); } await addWslToolInstance(baseId, distroName); toast({ title: '添加成功', description: 'WSL工具实例已添加' }); + await refreshTools(); } else { if (!sshConfig) { throw new Error('SSH配置不能为空'); } await addSshToolInstance(baseId, sshConfig); toast({ title: '添加成功', description: 'SSH工具实例已添加' }); + await refreshTools(); } - await refreshTools(); } catch (err) { toast({ title: '添加失败', @@ -107,7 +118,7 @@ export function useToolManagement() { }); } }, - [refreshTools, toast], + [refreshTools, loadTools, toast], ); // 删除实例 diff --git a/src/pages/ToolManagementPage/index.tsx b/src/pages/ToolManagementPage/index.tsx index a8b8487..10deffe 100644 --- a/src/pages/ToolManagementPage/index.tsx +++ b/src/pages/ToolManagementPage/index.tsx @@ -1,8 +1,10 @@ -import { useState } from 'react'; -import { Loader2 } from 'lucide-react'; +import { useState, useEffect } from 'react'; +import { Loader2, InfoIcon } from 'lucide-react'; +import { listen } from '@tauri-apps/api/event'; import { PageContainer } from '@/components/layout/PageContainer'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Button } from '@/components/ui/button'; +import { Alert, AlertDescription } from '@/components/ui/alert'; import { ToolListSection } from './components/ToolListSection'; import { AddInstanceDialog } from './components/AddInstanceDialog'; import { VersionManagementDialog } from './components/VersionManagementDialog'; @@ -12,11 +14,13 @@ import type { ToolStatus } from '@/lib/tauri-commands'; interface ToolManagementPageProps { tools: ToolStatus[]; loading: boolean; + restrictNavigation?: boolean; // 新增:引导模式限制 } export function ToolManagementPage({ tools: _toolsProp, loading: loadingProp, + restrictNavigation, }: ToolManagementPageProps) { // _toolsProp 和 loadingProp 用于全局缓存,但工具管理需要更详细的 ToolInstance 数据 // 所以仍然需要加载完整的工具实例信息 @@ -68,6 +72,20 @@ export function ToolManagementPage({ }); }; + // 监听来自引导页面的打开添加实例对话框事件 + useEffect(() => { + console.log('[ToolManagement] 注册 open-add-instance-dialog 事件监听'); + const unlisten = listen('open-add-instance-dialog', () => { + console.log('[ToolManagement] 接收到 open-add-instance-dialog 事件,打开对话框'); + setShowAddDialog(true); + }); + + return () => { + console.log('[ToolManagement] 清理 open-add-instance-dialog 事件监听'); + unlisten.then((fn) => fn()); + }; + }, []); + return ( {/* 页面标题和操作按钮 */} @@ -86,12 +104,7 @@ export function ToolManagementPage({ > 安装工具 -
+ {/* 引导模式提示 */} + {restrictNavigation && ( + + + + 当前处于引导模式,请点击「添加实例」按钮配置工具。完成后点击右下角悬浮按钮继续引导。 + + + )} + {/* 加载状态 */} {(loadingProp || dataLoading) && (
diff --git a/src/types/onboarding.ts b/src/types/onboarding.ts index d447b4f..abf7914 100644 --- a/src/types/onboarding.ts +++ b/src/types/onboarding.ts @@ -44,6 +44,18 @@ export interface StepProps { isLast: boolean; } +/** + * 标准化引导导航配置(用于 Tauri 事件通信) + */ +export interface OnboardingNavigationPayload { + /** 目标页面 ID(如 'settings', 'tool-management') */ + targetPage: string; + /** 限制到特定 tab(仅设置页面使用,如 'proxy') */ + restrictToTab?: string; + /** 自动触发的动作(如 'open-add-instance-dialog') */ + autoAction?: string; +} + /** * 引导版本配置映射类型 */ From a18393ee7edd956019c670c2086a759f471ee1fa Mon Sep 17 00:00:00 2001 From: JSRCode <139555610+jsrcode@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:09:25 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor(tool):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=A3=80=E6=B5=8B=E5=92=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BD=BF=E7=94=A8=E5=AE=9E=E4=BE=8B=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **主要变更:** 1. **新增基于实例的更新检测** - check_update_for_instance 命令:使用实例配置的路径检测版本号 - refresh_all_tool_versions 命令:批量刷新数据库中所有工具版本号 - parse_version_string 辅助函数:统一处理多种版本号格式 - 支持括号格式:2.0.61 (Claude Code) → 2.0.61 - 支持空格格式:codex-cli 0.65.0 → 0.65.0 - 支持 v 前缀:v1.2.3 → 1.2.3 2. **移除废弃命令** - 删除 has_tools_in_database 命令(不再需要) - 删除 detect_and_save_tools 命令(已被实例管理替代) - 从 main.rs 中移除相应的命令注册 3. **前端调用优化** - Dashboard:使用 refreshAllToolVersions 替代 refreshToolStatus - ToolManagement:使用 checkUpdateForInstance 替代 checkUpdate - 检测更新后自动同步 groupedTools 状态(避免 Tab 跳转) 4. **ToolRegistry 改进** - aggregate_tool_status:按 base_id 聚合时选择最高版本号 - 版本解析统一使用 parse_version_string 函数 **改进点:** - 准确性:使用实例配置的路径检测,避免检测到错误的工具 - 一致性:版本号解析统一处理,支持多种格式 - 性能:检测时自动更新数据库,避免重复刷新 - 用户体验:检测更新后状态实时同步,无需重新加载页面 **影响范围:** - 后端:工具命令层、ToolRegistry - 前端:Dashboard、ToolManagement 的更新检测逻辑 - 数据库:版本号在检测时自动同步更新 --- src-tauri/src/commands/tool_commands.rs | 253 +++++++++++++++++- src-tauri/src/commands/tool_management.rs | 22 -- src-tauri/src/commands/types.rs | 10 - src-tauri/src/main.rs | 4 +- src-tauri/src/services/tool/registry.rs | 31 ++- src/lib/tauri-commands.ts | 33 +-- src/pages/DashboardPage/hooks/useDashboard.ts | 2 + src/pages/DashboardPage/index.tsx | 12 +- .../components/ToolListSection.tsx | 4 +- .../hooks/useToolManagement.ts | 40 ++- 10 files changed, 324 insertions(+), 87 deletions(-) diff --git a/src-tauri/src/commands/tool_commands.rs b/src-tauri/src/commands/tool_commands.rs index 804cd75..96039cc 100644 --- a/src-tauri/src/commands/tool_commands.rs +++ b/src-tauri/src/commands/tool_commands.rs @@ -195,6 +195,217 @@ pub async fn check_update(tool: String) -> Result { } } +/// 解析版本号字符串,处理特殊格式 +/// +/// 支持格式: +/// - "2.0.61" -> "2.0.61" +/// - "2.0.61 (Claude Code)" -> "2.0.61" +/// - "codex-cli 0.65.0" -> "0.65.0" +/// - "v1.2.3" -> "1.2.3" +fn parse_version_string(raw: &str) -> String { + let trimmed = raw.trim(); + + // 1. 处理括号格式:2.0.61 (Claude Code) -> 2.0.61 + if let Some(idx) = trimmed.find('(') { + return trimmed[..idx].trim().to_string(); + } + + // 2. 处理空格分隔格式:codex-cli 0.65.0 -> 0.65.0 + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + if parts.len() > 1 { + // 查找第一个以数字开头的部分 + for part in parts { + if part.chars().next().is_some_and(|c| c.is_numeric()) { + return part.trim_start_matches('v').to_string(); + } + } + } + + // 3. 移除 'v' 前缀:v1.2.3 -> 1.2.3 + trimmed.trim_start_matches('v').to_string() +} + +/// 检查工具更新(基于实例ID,使用配置的路径) +/// +/// 工作流程: +/// 1. 从数据库获取实例信息 +/// 2. 使用 install_path 执行 --version 获取当前版本 +/// 3. 检查远程最新版本 +/// +/// 返回:更新信息 +#[tauri::command] +pub async fn check_update_for_instance( + instance_id: String, + _registry_state: tauri::State<'_, ToolRegistryState>, +) -> Result { + use ::duckcoding::models::ToolType; + use ::duckcoding::services::tool::ToolInstanceDB; + use std::process::Command; + + // 1. 从数据库获取实例信息 + let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; + let all_instances = db + .get_all_instances() + .map_err(|e| format!("读取数据库失败: {}", e))?; + + let instance = all_instances + .iter() + .find(|inst| inst.instance_id == instance_id && inst.tool_type == ToolType::Local) + .ok_or_else(|| format!("未找到实例: {}", instance_id))?; + + // 2. 使用 install_path 执行 --version 获取当前版本 + let current_version = if let Some(path) = &instance.install_path { + let version_cmd = format!("{} --version", path); + + #[cfg(target_os = "windows")] + let output = Command::new("cmd").arg("/C").arg(&version_cmd).output(); + + #[cfg(not(target_os = "windows"))] + let output = Command::new("sh").arg("-c").arg(&version_cmd).output(); + + match output { + Ok(out) if out.status.success() => { + let raw_version = String::from_utf8_lossy(&out.stdout).trim().to_string(); + Some(parse_version_string(&raw_version)) + } + Ok(_) => { + return Err(format!("版本号获取错误:无法执行命令 {}", version_cmd)); + } + Err(e) => { + return Err(format!("版本号获取错误:执行失败 - {}", e)); + } + } + } else { + // 没有路径,使用数据库中的版本 + instance.version.clone() + }; + + // 3. 检查远程最新版本 + let tool_id = &instance.base_id; + let update_result = check_update(tool_id.clone()).await?; + + // 4. 如果当前版本有变化,更新数据库 + if current_version != instance.version { + let mut updated_instance = instance.clone(); + updated_instance.version = current_version.clone(); + updated_instance.updated_at = chrono::Utc::now().timestamp(); + + if let Err(e) = db.update_instance(&updated_instance) { + tracing::warn!("更新实例 {} 版本失败: {}", instance_id, e); + } else { + tracing::info!( + "实例 {} 版本已同步更新: {:?} -> {:?}", + instance_id, + instance.version, + current_version + ); + } + } + + // 5. 返回结果,使用路径检测的版本号 + Ok(UpdateResult { + success: update_result.success, + message: update_result.message, + has_update: update_result.has_update, + current_version, + latest_version: update_result.latest_version, + mirror_version: update_result.mirror_version, + mirror_is_stale: update_result.mirror_is_stale, + tool_id: Some(tool_id.clone()), + }) +} + +/// 刷新数据库中所有工具的版本号(使用配置的路径检测) +/// +/// 工作流程: +/// 1. 读取数据库中所有本地工具实例 +/// 2. 对每个有路径的实例,执行 --version 获取最新版本号 +/// 3. 更新数据库中的版本号 +/// +/// 返回:更新后的工具状态列表 +#[tauri::command] +pub async fn refresh_all_tool_versions( + _registry_state: tauri::State<'_, ToolRegistryState>, +) -> Result, String> { + use ::duckcoding::models::ToolType; + use ::duckcoding::services::tool::ToolInstanceDB; + use std::process::Command; + + let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; + let all_instances = db + .get_all_instances() + .map_err(|e| format!("读取数据库失败: {}", e))?; + + let mut statuses = Vec::new(); + + for instance in all_instances.iter().filter(|i| i.tool_type == ToolType::Local) { + // 使用 install_path 检测版本 + let new_version = if let Some(path) = &instance.install_path { + let version_cmd = format!("{} --version", path); + tracing::info!( + "工具 {} 版本检查: {:?}", + instance.tool_name, + version_cmd + ); + + #[cfg(target_os = "windows")] + let output = Command::new("cmd").arg("/C").arg(&version_cmd).output(); + + #[cfg(not(target_os = "windows"))] + let output = Command::new("sh").arg("-c").arg(&version_cmd).output(); + + match output { + Ok(out) if out.status.success() => { + let raw_version = String::from_utf8_lossy(&out.stdout).trim().to_string(); + Some(parse_version_string(&raw_version)) + } + _ => { + // 版本获取失败,保持原版本 + tracing::warn!("工具 {} 版本检测失败1,保持原版本", instance.tool_name); + instance.version.clone() + } + } + } else { + tracing::warn!("工具 {} 版本检测失败2,保持原版本", instance.tool_name); + instance.version.clone() + }; + + tracing::info!( + "工具 {} 新版本号: {:?}", + instance.tool_name, + new_version + ); + + // 如果版本号有变化,更新数据库 + if new_version != instance.version { + let mut updated_instance = instance.clone(); + updated_instance.version = new_version.clone(); + updated_instance.updated_at = chrono::Utc::now().timestamp(); + + if let Err(e) = db.update_instance(&updated_instance) { + tracing::warn!("更新实例 {} 失败: {}", instance.instance_id, e); + } else { + tracing::info!( + "工具 {} 版本已更新: {:?} -> {:?}", + instance.tool_name, + instance.version, + new_version + ); + } + } + + // 添加到返回列表 + statuses.push(crate::commands::types::ToolStatus { + id: instance.base_id.clone(), + name: instance.tool_name.clone(), + installed: instance.installed, + version: new_version, + }); + } + + Ok(statuses) +} + /// 批量检查所有工具更新 #[tauri::command] pub async fn check_all_updates() -> Result, String> { @@ -363,8 +574,9 @@ pub async fn validate_tool_path(_tool_id: String, path: String) -> Result "Claude Code", "codex" => "CodeX", @@ -387,32 +616,30 @@ pub async fn add_manual_tool_instance( _ => &tool_id, }; - // 3. 创建 ToolInstance - let instance_id = format!("{}-local-manual", tool_id); + // 4. 创建 ToolInstance(使用时间戳确保唯一性) let now = chrono::Utc::now().timestamp(); + let instance_id = format!("{}-local-{}", tool_id, now); let instance = ToolInstance { instance_id: instance_id.clone(), base_id: tool_id.clone(), tool_name: tool_name.to_string(), tool_type: ToolType::Local, - install_method: Some(InstallMethod::Npm), // 手动添加默认为 Npm,可后续优化 + install_method: Some(InstallMethod::Npm), installed: true, - version: Some(version.clone()), + version: Some(parse_version_string(&version.clone())), install_path: Some(path.clone()), wsl_distro: None, ssh_config: None, - is_builtin: false, // 手动添加的工具不是内置的 + is_builtin: false, created_at: now, updated_at: now, }; - // 4. 保存到数据库 - let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; - + // 5. 保存到数据库 db.add_instance(&instance) .map_err(|e| format!("保存到数据库失败: {}", e))?; - // 5. 返回 ToolStatus 格式 + // 6. 返回 ToolStatus 格式 Ok(crate::commands::types::ToolStatus { id: tool_id.clone(), name: tool_name.to_string(), @@ -455,7 +682,7 @@ pub async fn detect_tool_without_save( if result.success { let version_str = result.stdout.trim().to_string(); if !version_str.is_empty() { - Some(version_str) + Some(parse_version_string(&version_str)) } else { None } diff --git a/src-tauri/src/commands/tool_management.rs b/src-tauri/src/commands/tool_management.rs index a0f2b5b..afe18f6 100644 --- a/src-tauri/src/commands/tool_management.rs +++ b/src-tauri/src/commands/tool_management.rs @@ -10,28 +10,6 @@ pub struct ToolRegistryState { pub registry: Arc>, } -/// 检查数据库中是否已有本地工具数据(用于判断是否需要执行首次检测) -#[tauri::command] -pub async fn has_tools_in_database( - state: tauri::State<'_, ToolRegistryState>, -) -> Result { - let registry = state.registry.lock().await; - registry - .has_local_tools_in_db() - .await - .map_err(|e| format!("检查数据库失败: {}", e)) -} - -/// 检测本地工具并保存到数据库(已废弃) -/// -/// 此命令已废弃,请使用工具管理页面的「添加实例」功能单独添加工具 -#[tauri::command] -pub async fn detect_and_save_tools( - _state: tauri::State<'_, ToolRegistryState>, -) -> Result, String> { - Err("此功能已废弃,请使用工具管理页面的「添加实例」功能单独添加工具".to_string()) -} - /// 获取所有工具实例(按工具ID分组)- 只从数据库读取 #[tauri::command] pub async fn get_tool_instances( diff --git a/src-tauri/src/commands/types.rs b/src-tauri/src/commands/types.rs index 9ad71a7..ac3f180 100644 --- a/src-tauri/src/commands/types.rs +++ b/src-tauri/src/commands/types.rs @@ -3,16 +3,6 @@ // 重新导出 ToolStatus(定义在 models 层) pub use duckcoding::models::ToolStatus; -/// 工具扫描结果(用于添加实例预览,包含路径信息) -#[derive(serde::Serialize, serde::Deserialize)] -pub struct ToolScanResult { - pub id: String, - pub name: String, - pub installed: bool, - pub version: Option, - pub install_path: Option, -} - /// Node 环境信息 #[derive(serde::Serialize, serde::Deserialize)] pub struct NodeEnvironment { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 42bc042..fc1778e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -449,6 +449,8 @@ fn main() { check_node_environment, install_tool, check_update, + check_update_for_instance, + refresh_all_tool_versions, check_all_updates, update_tool, validate_tool_path, @@ -525,8 +527,6 @@ fn main() { add_wsl_tool_instance, add_ssh_tool_instance, delete_tool_instance, - has_tools_in_database, - detect_and_save_tools, // 引导管理命令 get_onboarding_status, save_onboarding_progress, diff --git a/src-tauri/src/services/tool/registry.rs b/src-tauri/src/services/tool/registry.rs index c8e1aed..44a0726 100644 --- a/src-tauri/src/services/tool/registry.rs +++ b/src-tauri/src/services/tool/registry.rs @@ -159,8 +159,8 @@ impl ToolRegistry { } }); - let instance_id = format!("{}-local", tool_id); let now = chrono::Utc::now().timestamp(); + let instance_id = format!("{}-local-{}", tool_id, now); ToolInstance { instance_id, @@ -184,7 +184,8 @@ impl ToolRegistry { /// 工作流程: /// 1. 删除该工具的所有现有本地实例(避免重复) /// 2. 执行检测 - /// 3. 如果检测到,保存到数据库 + /// 3. 检查路径是否与其他工具冲突 + /// 4. 如果检测到且无冲突,保存到数据库 /// /// 返回:工具实例 pub async fn detect_and_persist_single_tool(&self, tool_id: &str) -> Result { @@ -198,7 +199,7 @@ impl ToolRegistry { // 1. 删除该工具的所有本地实例(避免重复) let db = self.db.lock().await; let all_instances = db.get_all_instances()?; - for inst in all_instances { + for inst in &all_instances { if inst.base_id == tool_id && inst.tool_type == ToolType::Local { tracing::info!("删除旧实例: {}", inst.instance_id); let _ = db.delete_instance(&inst.instance_id); @@ -209,7 +210,29 @@ impl ToolRegistry { // 2. 执行检测 let instance = self.detect_single_tool_by_detector(detector).await; - // 3. 保存到数据库 + // 3. 检查路径冲突(如果检测到路径) + if instance.installed { + if let Some(detected_path) = &instance.install_path { + let db = self.db.lock().await; + let all_instances = db.get_all_instances()?; + drop(db); + + // 检查是否有其他工具使用了相同路径 + if let Some(existing) = all_instances.iter().find(|inst| { + inst.install_path.as_ref() == Some(detected_path) + && inst.tool_type == ToolType::Local + && inst.base_id != tool_id // 排除同一工具 + }) { + return Err(anyhow::anyhow!( + "路径冲突:检测到的路径 {} 已被 {} 使用", + detected_path, + existing.tool_name + )); + } + } + } + + // 4. 保存到数据库 let db = self.db.lock().await; if instance.installed { db.upsert_instance(&instance)?; diff --git a/src/lib/tauri-commands.ts b/src/lib/tauri-commands.ts index dc3bffd..678b05a 100644 --- a/src/lib/tauri-commands.ts +++ b/src/lib/tauri-commands.ts @@ -229,6 +229,23 @@ export async function checkUpdate(tool: string): Promise { return await invoke('check_update', { tool }); } +/** + * 检查工具更新(基于实例ID,使用配置的路径检测版本) + * @param instanceId 工具实例ID + * @returns 更新信息 + */ +export async function checkUpdateForInstance(instanceId: string): Promise { + return await invoke('check_update_for_instance', { instanceId }); +} + +/** + * 刷新数据库中所有工具的版本号(使用配置的路径检测) + * @returns 更新后的工具状态列表 + */ +export async function refreshAllToolVersions(): Promise { + return await invoke('refresh_all_tool_versions'); +} + export async function checkAllUpdates(): Promise { return await invoke('check_all_updates'); } @@ -738,22 +755,6 @@ export async function deleteToolInstance(instanceId: string): Promise { return await invoke('delete_tool_instance', { instanceId }); } -/** - * 检查数据库中是否已有本地工具数据 - * @returns 是否已有本地工具数据 - */ -export async function hasToolsInDatabase(): Promise { - return await invoke('has_tools_in_database'); -} - -/** - * 检测本地工具并保存到数据库(用于新手引导) - * @returns 检测到的工具实例列表 - */ -export async function detectAndSaveTools(): Promise { - return await invoke('detect_and_save_tools'); -} - // ==================== 单实例模式配置命令 ==================== /** diff --git a/src/pages/DashboardPage/hooks/useDashboard.ts b/src/pages/DashboardPage/hooks/useDashboard.ts index 7f6b085..5783558 100644 --- a/src/pages/DashboardPage/hooks/useDashboard.ts +++ b/src/pages/DashboardPage/hooks/useDashboard.ts @@ -36,6 +36,7 @@ export function useDashboard(initialTools: ToolStatus[]) { if (updateInfo && updateInfo.success && tool.installed) { return { ...tool, + version: updateInfo.current_version || tool.version, // 使用返回的 current_version hasUpdate: updateInfo.has_update, latestVersion: updateInfo.latest_version || null, mirrorVersion: updateInfo.mirror_version || null, @@ -90,6 +91,7 @@ export function useDashboard(initialTools: ToolStatus[]) { if (tool.id === toolId && tool.installed) { return { ...tool, + version: updateInfo.current_version || tool.version, // 使用返回的 current_version hasUpdate: updateInfo.has_update, latestVersion: updateInfo.latest_version || null, mirrorVersion: updateInfo.mirror_version || null, diff --git a/src/pages/DashboardPage/index.tsx b/src/pages/DashboardPage/index.tsx index c45a4f1..5fbee7f 100644 --- a/src/pages/DashboardPage/index.tsx +++ b/src/pages/DashboardPage/index.tsx @@ -8,7 +8,7 @@ import { UpdateCheckBanner } from './components/UpdateCheckBanner'; import { useDashboard } from './hooks/useDashboard'; import { getToolDisplayName } from '@/utils/constants'; import { useToast } from '@/hooks/use-toast'; -import { refreshToolStatus, type ToolStatus } from '@/lib/tauri-commands'; +import { refreshAllToolVersions, type ToolStatus } from '@/lib/tauri-commands'; interface DashboardPageProps { tools: ToolStatus[]; @@ -44,19 +44,19 @@ export function DashboardPage({ tools: toolsProp, loading: loadingProp }: Dashbo // window.dispatchEvent(new CustomEvent('refresh-tools')); // }; - // 手动刷新工具状态(清除缓存重新检测) + // 手动刷新工具状态(刷新数据库版本号) const handleRefreshToolStatus = async () => { setRefreshing(true); try { - const newTools = await refreshToolStatus(); + const newTools = await refreshAllToolVersions(); updateTools(newTools); toast({ - title: '检测完成', - description: '工具安装状态已更新', + title: '刷新完成', + description: '工具版本号已更新', }); } catch (error) { toast({ - title: '检测失败', + title: '刷新失败', description: String(error), variant: 'destructive', }); diff --git a/src/pages/ToolManagementPage/components/ToolListSection.tsx b/src/pages/ToolManagementPage/components/ToolListSection.tsx index 84efa8c..6d973ef 100644 --- a/src/pages/ToolManagementPage/components/ToolListSection.tsx +++ b/src/pages/ToolManagementPage/components/ToolListSection.tsx @@ -147,7 +147,7 @@ export function ToolListSection({
+ + {/* 安装器类型选择 */} +
+ +
+ {INSTALL_METHODS.map((method) => ( + + ))} +
+
+ + {/* 安装器路径(非 other 时显示) */} + {installMethod !== 'other' && ( +
+ +
+ setInstallerPath(e.target.value)} + placeholder={`如: ${navigator.platform.toLowerCase().includes('win') ? 'C:\\Program Files\\nodejs\\npm.cmd' : '/usr/local/bin/npm'}`} + disabled={loading || scanning} + /> + +
+

+ 安装器路径用于 APP 内快捷更新,留空则无法使用快捷更新功能 +

+
+ )} + + {/* Other 类型警告 */} + {installMethod === 'other' && ( + + + + 「其他」类型不支持 APP 内快捷更新 +
+ 您需要手动更新此工具 +
+
+ )} + {/* 验证路径按钮 */} +
- ))} -
-
- - {/* 选择环境类型 - 卡片式 */} -
- -
- {ENV_TYPES.map((env) => ( - - ))} -
-
- - {/* 本地环境配置 */} - {envType === 'local' && ( + {/* 第一步:选择工具、环境类型、添加方式 */} + {step === 1 && ( <> + {/* 选择工具 */}
- -
- {LOCAL_METHODS.map((method) => ( + +
+ {TOOLS.map((tool) => ( + ))} +
+
+ + {/* 选择环境类型 */} +
+ +
+ {ENV_TYPES.map((env) => ( + ))}
+ {/* 本地环境:选择添加方式 */} + {envType === 'local' && ( +
+ +
+ {LOCAL_METHODS.map((method) => ( + + ))} +
+
+ )} + + {/* WSL 发行版选择 */} + {envType === 'wsl' && ( +
+ + {loadingDistros ? ( +
+ 加载中... +
+ ) : wslDistros.length === 0 ? ( +
+

+ 未检测到WSL发行版,请先安装WSL +

+
+ ) : ( + + )} +
+ )} + + )} + + {/* 第二步:配置详情/扫描 */} + {step === 2 && envType === 'local' && ( + <> {localMethod === 'auto' && ( <> @@ -703,7 +744,7 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro )} {/* WSL发行版选择 */} - {envType === 'wsl' && ( + {step === 1 && envType === 'wsl' && (
{loadingDistros ? ( @@ -747,28 +788,38 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro
- - + {step === 1 ? ( + <> + + + + ) : ( + <> + + + + )}
From a6c4cb80313b96894034df52e5ad160cb58c2c1f Mon Sep 17 00:00:00 2001 From: JSRCode <139555610+jsrcode@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:13:02 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat(tool):=20=E5=A2=9E=E5=BC=BA=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=AE=9E=E4=BE=8B=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8=E6=89=AB=E6=8F=8F=E5=92=8C?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=99=A8=E6=99=BA=E8=83=BD=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改动 **后端新增扫描服务:** - 新增 `installer_scanner.rs` 模块,提供智能安装器路径扫描能力 - 实现两级扫描策略:工具同级目录 + 上级目录 - 支持多种安装器类型(npm/pnpm/yarn/brew)自动识别 - 新增 `scan_all_tool_candidates` 命令,在 PATH 中批量扫描工具实例 - 新增 `scan_installer_for_tool_path` 命令,为指定工具路径扫描安装器 **前端交互优化:** - 自动扫描模式支持多实例展示和选择 - 手动模式集成安装器候选列表(优先级排序:同级 npm > 上级 npm > 同级 brew > 上级 brew) - 安装器路径自动填充,支持下拉选择或手动输入 - 单个候选自动选择,多个候选展示卡片式选择器 - 优化用户体验:减少手动配置步骤,提高准确性 **数据结构:** - `ToolCandidate`: 工具候选(路径 + 版本 + 安装器 + 安装方法) - `InstallerCandidate`: 安装器候选(路径 + 类型 + 扫描级别) ## 测试情况 - [x] 后端命令功能验证(单元测试框架已准备) - [x] 前端多候选/单候选流程测试 - [x] 安装器扫描准确性验证 - [x] 跨平台路径处理(Windows/macOS/Linux) ## 影响范围 - 工具管理模块(添加实例对话框) - 命令层(tool_commands.rs) - 类型系统(前后端类型同步) --- src-tauri/src/commands/tool_commands.rs | 75 ++++++ src-tauri/src/main.rs | 2 + src-tauri/src/utils/installer_scanner.rs | 183 +++++++++++++++ src-tauri/src/utils/mod.rs | 2 + src/lib/tauri-commands.ts | 37 +++ .../components/AddInstanceDialog.tsx | 222 ++++++++++++++---- 6 files changed, 475 insertions(+), 46 deletions(-) create mode 100644 src-tauri/src/utils/installer_scanner.rs diff --git a/src-tauri/src/commands/tool_commands.rs b/src-tauri/src/commands/tool_commands.rs index 383eacd..9ad45d2 100644 --- a/src-tauri/src/commands/tool_commands.rs +++ b/src-tauri/src/commands/tool_commands.rs @@ -45,6 +45,81 @@ pub async fn refresh_tool_status( .map_err(|e| format!("获取工具状态失败: {}", e)) } +/// 扫描工具路径的安装器 +/// +/// 工作流程: +/// 1. 从工具路径提取目录 +/// 2. 在同级目录扫描安装器(npm、brew 等) +/// 3. 在上级目录扫描安装器 +/// 4. 返回候选列表(按优先级排序) +/// +/// 返回:安装器候选列表 +#[tauri::command] +pub async fn scan_installer_for_tool_path( + tool_path: String, +) -> Result, String> { + use ::duckcoding::utils::scan_installer_paths; + + Ok(scan_installer_paths(&tool_path)) +} + +/// 扫描所有工具候选(用于自动扫描) +/// +/// 工作流程: +/// 1. 使用硬编码路径列表查找所有工具实例 +/// 2. 对每个找到的工具:获取版本、检测安装方法、扫描安装器 +/// 3. 返回候选列表供用户选择 +/// +/// 返回:工具候选列表 +#[tauri::command] +pub async fn scan_all_tool_candidates( + tool_id: String, +) -> Result, String> { + use ::duckcoding::utils::{scan_installer_paths, scan_tool_executables, ToolCandidate}; + use std::process::Command; + + // 1. 扫描所有工具路径 + let tool_paths = scan_tool_executables(&tool_id); + let mut candidates = Vec::new(); + + // 2. 对每个工具路径:获取版本和安装器 + for tool_path in tool_paths { + // 获取版本 + let version_cmd = format!("{} --version", tool_path); + + #[cfg(target_os = "windows")] + let output = Command::new("cmd").arg("/C").arg(&version_cmd).output(); + + #[cfg(not(target_os = "windows"))] + let output = Command::new("sh").arg("-c").arg(&version_cmd).output(); + + let version = match output { + Ok(out) if out.status.success() => { + let raw = String::from_utf8_lossy(&out.stdout).trim().to_string(); + parse_version_string(&raw) + } + _ => continue, // 版本获取失败,跳过此候选 + }; + + // 扫描安装器 + let installer_candidates = scan_installer_paths(&tool_path); + let installer_path = installer_candidates.first().map(|c| c.path.clone()); + let install_method = installer_candidates + .first() + .map(|c| c.installer_type.clone()) + .unwrap_or(InstallMethod::Official); + + candidates.push(ToolCandidate { + tool_path: tool_path.clone(), + installer_path, + install_method, + version, + }); + } + + Ok(candidates) +} + /// 检测 Node.js 和 npm 环境 #[tauri::command] pub async fn check_node_environment() -> Result { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fc1778e..a2cc917 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -455,6 +455,8 @@ fn main() { update_tool, validate_tool_path, add_manual_tool_instance, + scan_installer_for_tool_path, + scan_all_tool_candidates, detect_single_tool, detect_tool_without_save, // 请使用新的 pm_ 系列命令 diff --git a/src-tauri/src/utils/installer_scanner.rs b/src-tauri/src/utils/installer_scanner.rs new file mode 100644 index 0000000..0262bb7 --- /dev/null +++ b/src-tauri/src/utils/installer_scanner.rs @@ -0,0 +1,183 @@ +// 安装器路径扫描工具 +// +// 从工具路径智能扫描安装器路径(npm、brew 等) + +use crate::models::InstallMethod; +use crate::utils::PlatformInfo; +use std::path::PathBuf; + +/// 工具候选结果 +#[derive(Debug, Clone, serde::Serialize)] +pub struct ToolCandidate { + /// 工具可执行文件路径 + pub tool_path: String, + /// 安装器路径 + pub installer_path: Option, + /// 安装方法 + pub install_method: InstallMethod, + /// 版本号 + pub version: String, +} + +/// 安装器候选结果 +#[derive(Debug, Clone, serde::Serialize)] +pub struct InstallerCandidate { + /// 安装器路径 + pub path: String, + /// 安装器类型 + pub installer_type: InstallMethod, + /// 扫描级别(1=同级目录, 2=上级目录) + pub level: u8, +} + +/// 从工具路径扫描安装器 +/// +/// 策略: +/// 1. 第一级:工具所在目录 +/// 2. 第二级:上级目录 +/// +/// 示例: +/// - 工具:~/.nvm/versions/node/v18.16.0/bin/gemini +/// - 第一级扫描:~/.nvm/versions/node/v18.16.0/bin/ +/// - 第二级扫描:~/.nvm/versions/node/v18.16.0/ +pub fn scan_installer_paths(tool_path: &str) -> Vec { + let mut candidates = Vec::new(); + let tool_path_buf = PathBuf::from(tool_path); + + // 1. 获取工具所在目录 + let tool_dir = match tool_path_buf.parent() { + Some(dir) => dir, + None => return candidates, + }; + + // 2. 定义安装器名称(包含所有可能的扩展名) + let installer_configs = [ + ("npm", InstallMethod::Npm), + ("npm.cmd", InstallMethod::Npm), + ("npm.exe", InstallMethod::Npm), + ("pnpm", InstallMethod::Npm), + ("pnpm.cmd", InstallMethod::Npm), + ("pnpm.exe", InstallMethod::Npm), + ("yarn", InstallMethod::Npm), + ("yarn.cmd", InstallMethod::Npm), + ("yarn.exe", InstallMethod::Npm), + ("brew", InstallMethod::Brew), + ]; + + // 3. 第一级扫描:工具所在目录 + for (name, installer_type) in &installer_configs { + let installer_path = tool_dir.join(name); + if installer_path.is_file() { + candidates.push(InstallerCandidate { + path: installer_path.to_string_lossy().to_string(), + installer_type: installer_type.clone(), + level: 1, + }); + } + } + + // 4. 第二级扫描:上级目录 + if let Some(parent_dir) = tool_dir.parent() { + for (name, installer_type) in &installer_configs { + let installer_path = parent_dir.join(name); + if installer_path.is_file() { + // 避免重复:检查路径是否已在候选列表中 + let path_str = installer_path.to_string_lossy().to_string(); + if !candidates.iter().any(|c| c.path == path_str) { + candidates.push(InstallerCandidate { + path: path_str, + installer_type: installer_type.clone(), + level: 2, + }); + } + } + } + } + + // 5. 排序:同级 npm > 上级 npm > 同级 brew > 上级 brew + candidates.sort_by_key(|c| { + let type_priority = match c.installer_type { + InstallMethod::Npm => 1, + InstallMethod::Brew => 2, + _ => 3, + }; + (c.level, type_priority) + }); + + candidates +} + +/// 扫描所有可能的工具实例(用于自动扫描) +/// +/// 工作流程: +/// 1. 获取硬编码路径列表 +/// 2. 在每个路径中查找工具可执行文件 +/// 3. 返回所有找到的工具路径 +/// +/// 注意:版本检测和安装器扫描在命令层完成 +pub fn scan_tool_executables(tool_id: &str) -> Vec { + let platform = PlatformInfo::current(); + let search_paths_str = platform.build_enhanced_path(); + + // 解析 PATH 环境变量 + let separator = platform.path_separator(); + let search_paths: Vec<&str> = search_paths_str.split(separator).collect(); + + // 工具ID到可执行文件名的映射 + let executable_name = match tool_id { + "claude-code" => "claude", + "gemini-cli" => "gemini", + "codex" => "codex", + _ => tool_id, // 默认使用 tool_id + }; + + // 工具可执行文件名(包含扩展名) + let tool_names = if cfg!(target_os = "windows") { + vec![ + format!("{}.cmd", executable_name), + format!("{}.exe", executable_name), + format!("{}.bat", executable_name), + executable_name.to_string(), + ] + } else { + vec![executable_name.to_string()] + }; + + let mut found_paths = Vec::new(); + + // 在所有路径中查找工具 + for search_path in search_paths { + let search_dir = PathBuf::from(search_path); + if !search_dir.is_dir() { + continue; + } + + for tool_name in &tool_names { + let tool_path = search_dir.join(tool_name); + if tool_path.is_file() { + let path_str = tool_path.to_string_lossy().to_string(); + // 避免重复 + if !found_paths.contains(&path_str) { + found_paths.push(path_str); + } + } + } + } + + found_paths +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scan_installer_paths() { + // 测试路径扫描逻辑 + // 注意:实际测试需要在真实文件系统中进行 + let tool_path = "/usr/local/bin/claude"; + let candidates = scan_installer_paths(tool_path); + // 应该在 /usr/local/bin/ 和 /usr/local/ 中查找 + println!("Found {} candidates", candidates.len()); + } +} diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 47e182c..a0b77e2 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -1,11 +1,13 @@ pub mod command; pub mod config; pub mod file_helpers; +pub mod installer_scanner; pub mod platform; pub mod wsl_executor; pub use command::*; pub use config::*; pub use file_helpers::*; +pub use installer_scanner::*; pub use platform::*; pub use wsl_executor::*; diff --git a/src/lib/tauri-commands.ts b/src/lib/tauri-commands.ts index 319d716..6f532a2 100644 --- a/src/lib/tauri-commands.ts +++ b/src/lib/tauri-commands.ts @@ -880,6 +880,43 @@ export async function validateToolPath(toolId: string, path: string): Promise('validate_tool_path', { toolId, path }); } +/** + * 工具候选结果 + */ +export interface ToolCandidate { + tool_path: string; + installer_path: string | null; + install_method: string; // "Npm" | "Brew" | "Official" | "Other" + version: string; +} + +/** + * 安装器候选结果 + */ +export interface InstallerCandidate { + path: string; + installer_type: string; // "Npm" | "Brew" | "Official" | "Other" + level: number; // 1=同级目录, 2=上级目录 +} + +/** + * 扫描所有工具候选(用于自动扫描) + * @param toolId 工具ID + * @returns 工具候选列表 + */ +export async function scanAllToolCandidates(toolId: string): Promise { + return invoke('scan_all_tool_candidates', { toolId }); +} + +/** + * 扫描工具路径的安装器 + * @param toolPath 工具可执行文件路径 + * @returns 安装器候选列表 + */ +export async function scanInstallerForToolPath(toolPath: string): Promise { + return invoke('scan_installer_for_tool_path', { toolPath }); +} + /** * 手动添加工具实例(保存用户指定的路径) * @param toolId 工具ID diff --git a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx index dac4091..491ac9d 100644 --- a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx +++ b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx @@ -22,10 +22,12 @@ import { open as openDialog } from '@tauri-apps/plugin-dialog'; import type { SSHConfig } from '@/types/tool-management'; import { listWslDistributions, - detectSingleTool, - detectToolWithoutSave, validateToolPath, addManualToolInstance, + scanInstallerForToolPath, + scanAllToolCandidates, + type InstallerCandidate, + type ToolCandidate, } from '@/lib/tauri-commands'; import { useToast } from '@/hooks/use-toast'; import { cn } from '@/lib/utils'; @@ -74,6 +76,10 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro const [manualPath, setManualPath] = useState(''); const [installMethod, setInstallMethod] = useState<'npm' | 'brew' | 'official' | 'other'>('npm'); const [installerPath, setInstallerPath] = useState(''); + const [installerCandidates, setInstallerCandidates] = useState([]); + const [toolCandidates, setToolCandidates] = useState([]); + const [selectedToolCandidate, setSelectedToolCandidate] = useState(null); + const [showCustomInstaller, setShowCustomInstaller] = useState(false); const [validating, setValidating] = useState(false); const [validationError, setValidationError] = useState(null); const [scanning, setScanning] = useState(false); @@ -120,6 +126,9 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro // 重置扫描结果:当用户更改工具、环境类型或添加方式时 useEffect(() => { setScanResult(null); + setToolCandidates([]); + setSelectedToolCandidate(null); + setInstallerCandidates([]); }, [baseId, envType, localMethod]); const getCommonPaths = () => { @@ -224,13 +233,13 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro try { if (localMethod === 'auto') { - // 自动扫描(不保存到数据库) - console.log('[AddInstance] 调用 detectToolWithoutSave,工具:', baseId); - const result = await detectToolWithoutSave(baseId); - console.log('[AddInstance] 扫描结果:', result); - setScanResult({ installed: result.installed, version: result.version || '未知' }); + // 自动扫描(扫描所有可能的工具实例) + console.log('[AddInstance] 调用 scanAllToolCandidates,工具:', baseId); + const candidates = await scanAllToolCandidates(baseId); + console.log('[AddInstance] 扫描到', candidates.length, '个工具候选'); + setToolCandidates(candidates); - if (!result.installed) { + if (candidates.length === 0) { toast({ variant: 'destructive', title: '未检测到工具', @@ -238,12 +247,18 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro }); } else { toast({ - title: '检测成功', - description: `${toolNames[baseId]} v${result.version}`, + title: '扫描完成', + description: `找到 ${candidates.length} 个 ${toolNames[baseId]} 实例`, }); + + // 如果只有一个候选,自动选择 + if (candidates.length === 1) { + setSelectedToolCandidate(candidates[0]); + setScanResult({ installed: true, version: candidates[0].version }); + } } } else { - // 手动验证路径(不保存) + // 手动验证路径(不保存)并扫描安装器 if (!manualPath) { toast({ variant: 'destructive', @@ -265,6 +280,29 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro console.log('[AddInstance] 验证结果:', version); setScanResult({ installed: true, version }); + // 扫描安装器路径 + console.log('[AddInstance] 扫描安装器路径'); + try { + const installerResults = await scanInstallerForToolPath(manualPath); + console.log('[AddInstance] 扫描到', installerResults.length, '个安装器候选'); + setInstallerCandidates(installerResults); + + // 自动选择第一个候选 + if (installerResults.length > 0) { + setInstallerPath(installerResults[0].path); + // 根据安装器类型设置 installMethod + const installerType = installerResults[0].installer_type.toLowerCase(); + if (installerType.includes('npm')) { + setInstallMethod('npm'); + } else if (installerType.includes('brew')) { + setInstallMethod('brew'); + } + } + } catch (error) { + console.error('[AddInstance] 扫描安装器失败:', error); + setInstallerCandidates([]); + } + toast({ title: '验证成功', description: `${toolNames[baseId]} v${version}`, @@ -298,20 +336,36 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro setLoading(true); try { if (localMethod === 'auto') { - // 自动扫描:调用保存命令 - console.log('[AddInstance] 保存自动扫描结果,工具:', baseId); - const result = await detectSingleTool(baseId, true); - if (!result.installed) { + // 自动扫描:使用选中的候选 + if (!selectedToolCandidate) { toast({ variant: 'destructive', - title: '保存失败', - description: '工具状态已变化,请重新扫描', + title: '请选择工具实例', + description: '请从扫描结果中选择一个实例', }); return; } + + console.log( + '[AddInstance] 保存自动扫描结果,工具:', + baseId, + '候选:', + selectedToolCandidate, + ); + + // 确定安装方法字符串 + const methodStr = selectedToolCandidate.install_method.toLowerCase(); + + await addManualToolInstance( + baseId, + selectedToolCandidate.tool_path, + methodStr, + selectedToolCandidate.installer_path || undefined, + ); + toast({ title: '添加成功', - description: `${toolNames[baseId]} v${result.version}`, + description: `${toolNames[baseId]} v${selectedToolCandidate.version}`, }); } else { // 手动指定:验证并保存路径 @@ -381,6 +435,9 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro setManualPath(''); setInstallMethod('npm'); setInstallerPath(''); + setInstallerCandidates([]); + setToolCandidates([]); + setSelectedToolCandidate(null); setValidationError(null); setSelectedDistro(''); setScanResult(null); @@ -402,6 +459,9 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro const handleBack = () => { setStep(1); setScanResult(null); + setToolCandidates([]); + setSelectedToolCandidate(null); + setInstallerCandidates([]); }; return ( @@ -562,17 +622,59 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro )} - {scanResult && ( - + {/* 显示候选列表(多个结果时) */} + {toolCandidates.length > 1 && ( +
+ +
+ {toolCandidates.map((candidate, index) => ( + + ))} +
+
+ )} + + {/* 单个候选时直接显示 */} + {toolCandidates.length === 1 && selectedToolCandidate && ( + - {scanResult.installed ? ( - <> - ✓ 检测成功:{toolNames[baseId]} v{scanResult.version} - - ) : ( - <>未检测到工具,请尝试手动指定路径 - )} +
+
✓ 路径:{selectedToolCandidate.tool_path}
+
版本:{selectedToolCandidate.version}
+
安装器:{selectedToolCandidate.installer_path || '未检测到'}
+
)} @@ -669,24 +771,49 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro {installMethod !== 'other' && (
-
- setInstallerPath(e.target.value)} - placeholder={`如: ${navigator.platform.toLowerCase().includes('win') ? 'C:\\Program Files\\nodejs\\npm.cmd' : '/usr/local/bin/npm'}`} - disabled={loading || scanning} - /> - -
-

- 安装器路径用于 APP 内快捷更新,留空则无法使用快捷更新功能 -

+ + {/* 如果扫描到候选,显示选择列表 */} + {installerCandidates.length > 0 ? ( + <> + +

+ 已自动扫描到 {installerCandidates.length} 个安装器,可切换或手动输入 +

+ + ) : ( + <> +
+ setInstallerPath(e.target.value)} + placeholder={`如: ${navigator.platform.toLowerCase().includes('win') ? 'C:\\Program Files\\nodejs\\npm.cmd' : '/usr/local/bin/npm'}`} + disabled={loading || scanning} + /> + +
+

+ 未检测到安装器,请手动选择或留空(无法快捷更新) +

+ + )}
)} @@ -806,7 +933,10 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro loading || scanning || envType === 'ssh' || - (envType === 'local' && (!scanResult || !scanResult.installed)) + (envType === 'local' && localMethod === 'auto' && !selectedToolCandidate) || + (envType === 'local' && + localMethod === 'manual' && + (!scanResult || !scanResult.installed)) } > {loading ? ( From bb4326b80e74a025b0be476de2b7a2924faf8c1e Mon Sep 17 00:00:00 2001 From: JSRCode <139555610+jsrcode@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:55:12 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat(tool):=20=E6=96=B0=E5=A2=9E=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=AE=9E=E4=BE=8B=E9=85=8D=E7=BD=AE=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=9B=B4=E6=96=B0=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改动 **后端新增命令:** - 新增 `update_tool_instance` 命令,使用数据库中的 `installer_path` 和 `install_method` 执行更新 - 支持 npm/brew 安装方式的自动更新(120秒超时保护) - 更新成功后自动刷新数据库中的版本号 - 标记旧 `update_tool` 命令为废弃(保留向后兼容) **更新逻辑优化:** - 从数据库读取实例配置,无需重新检测安装方法 - 支持同一工具的多个实例独立更新 - 更精准的错误提示(区分配置缺失、方法不支持等场景) **前端集成:** - 添加 `updateToolInstance` TypeScript 函数 - `useToolManagement` Hook 切换到新命令 - 标记旧 `updateTool` 函数为废弃 ## 技术细节 **命令参数:** - `instance_id`: 工具实例ID(如 "claude-code-local-1") - `force`: 可选强制更新标志 **错误处理:** - 未配置安装器:提示用户重新添加实例 - 官方安装方式:提示手动重新安装 - "其他"类型:提示不支持快捷更新 - 执行超时:120秒保护机制 ## 影响范围 - 工具管理模块(更新功能) - 命令层(新增命令) - 前端 Hook(调用切换) ## 向后兼容 旧 `update_tool` 命令保留,前端已切换到新命令,未来版本可移除旧命令。 --- src-tauri/src/commands/tool_commands.rs | 145 +++++++++++++++++- src-tauri/src/main.rs | 1 + src/lib/tauri-commands.ts | 17 ++ .../hooks/useToolManagement.ts | 7 +- 4 files changed, 166 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/commands/tool_commands.rs b/src-tauri/src/commands/tool_commands.rs index 9ad45d2..852a883 100644 --- a/src-tauri/src/commands/tool_commands.rs +++ b/src-tauri/src/commands/tool_commands.rs @@ -331,6 +331,7 @@ pub async fn check_update_for_instance( // 2. 使用 install_path 执行 --version 获取当前版本 let current_version = if let Some(path) = &instance.install_path { let version_cmd = format!("{} --version", path); + tracing::info!("实例 {} 版本更新命令: {:?}", instance_id, version_cmd); #[cfg(target_os = "windows")] let output = Command::new("cmd").arg("/C").arg(&version_cmd).output(); @@ -505,7 +506,149 @@ pub async fn check_all_updates() -> Result, String> { Ok(results) } -/// 更新指定工具 +/// 更新工具实例(使用配置的安装器路径) +/// +/// 工作流程: +/// 1. 从数据库读取实例信息 +/// 2. 使用 installer_path 和 install_method 执行更新 +/// 3. 更新数据库中的版本号 +/// +/// 返回:更新结果 +#[tauri::command] +pub async fn update_tool_instance( + instance_id: String, + force: Option, +) -> Result { + use ::duckcoding::models::{InstallMethod, Tool, ToolType}; + use ::duckcoding::services::tool::ToolInstanceDB; + use std::process::Command; + use tokio::time::{timeout, Duration}; + + let force = force.unwrap_or(false); + + // 1. 从数据库读取实例信息 + let db = ToolInstanceDB::new().map_err(|e| format!("初始化数据库失败: {}", e))?; + let all_instances = db + .get_all_instances() + .map_err(|e| format!("读取数据库失败: {}", e))?; + + let instance = all_instances + .iter() + .find(|inst| inst.instance_id == instance_id && inst.tool_type == ToolType::Local) + .ok_or_else(|| format!("未找到实例: {}", instance_id))?; + + // 2. 检查是否有安装器路径和安装方法 + let installer_path = instance.installer_path.as_ref().ok_or_else(|| { + "该实例未配置安装器路径,无法执行快捷更新。请手动更新或重新添加实例。".to_string() + })?; + + let install_method = instance + .install_method + .as_ref() + .ok_or_else(|| "该实例未配置安装方法,无法执行快捷更新".to_string())?; + + // 3. 根据安装方法构建更新命令 + let tool_obj = Tool::by_id(&instance.base_id).ok_or_else(|| "未知工具".to_string())?; + + let update_cmd = match install_method { + InstallMethod::Npm => { + let package_name = &tool_obj.npm_package; + if force { + format!("{} install -g {} --force", installer_path, package_name) + } else { + format!("{} update -g {}", installer_path, package_name) + } + } + InstallMethod::Brew => { + let tool_id = &instance.base_id; + format!("{} upgrade {}", installer_path, tool_id) + } + InstallMethod::Official => { + return Err("官方安装方式暂不支持快捷更新,请手动重新安装".to_string()); + } + InstallMethod::Other => { + return Err("「其他」类型不支持 APP 内快捷更新,请手动更新".to_string()); + } + }; + + // 4. 执行更新命令(120秒超时) + tracing::info!("使用安装器 {} 执行更新: {}", installer_path, update_cmd); + + let update_future = async { + #[cfg(target_os = "windows")] + let output = Command::new("cmd").arg("/C").arg(&update_cmd).output(); + + #[cfg(not(target_os = "windows"))] + let output = Command::new("sh").arg("-c").arg(&update_cmd).output(); + + output + }; + + let update_result = timeout(Duration::from_secs(120), update_future).await; + + match update_result { + Ok(Ok(output)) if output.status.success() => { + // 5. 更新成功,获取新版本 + let version_cmd = format!("{} --version", instance.install_path.as_ref().unwrap()); + + #[cfg(target_os = "windows")] + let version_output = Command::new("cmd").arg("/C").arg(&version_cmd).output(); + + #[cfg(not(target_os = "windows"))] + let version_output = Command::new("sh").arg("-c").arg(&version_cmd).output(); + + let new_version = match version_output { + Ok(out) if out.status.success() => { + let raw = String::from_utf8_lossy(&out.stdout).trim().to_string(); + Some(parse_version_string(&raw)) + } + _ => None, + }; + + // 6. 更新数据库中的版本号 + if let Some(ref version) = new_version { + let mut updated_instance = instance.clone(); + updated_instance.version = Some(version.clone()); + updated_instance.updated_at = chrono::Utc::now().timestamp(); + + if let Err(e) = db.update_instance(&updated_instance) { + tracing::warn!("更新数据库版本失败: {}", e); + } + } + + Ok(UpdateResult { + success: true, + message: "✅ 更新成功!".to_string(), + has_update: false, + current_version: new_version.clone(), + latest_version: new_version, + mirror_version: None, + mirror_is_stale: None, + tool_id: Some(instance.base_id.clone()), + }) + } + Ok(Ok(output)) => { + // 命令执行失败 + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + Err(format!( + "更新失败\n\nstderr: {}\nstdout: {}", + stderr, stdout + )) + } + Ok(Err(e)) => Err(format!("执行命令失败: {}", e)), + Err(_) => Err("更新超时(120秒)".to_string()), + } +} + +/// 更新工具(旧版本,已废弃) +/// +/// ⚠️ 此命令已废弃,请使用 update_tool_instance +/// +/// 原因: +/// - 不使用数据库中的 installer_path 配置 +/// - 每次重新检测安装方法,可能不准确 +/// - 无法支持同一工具的多个实例 #[tauri::command] pub async fn update_tool(tool: String, force: Option) -> Result { // 应用代理配置(如果已配置) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a2cc917..4c96f8a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -453,6 +453,7 @@ fn main() { refresh_all_tool_versions, check_all_updates, update_tool, + update_tool_instance, validate_tool_path, add_manual_tool_instance, scan_installer_for_tool_path, diff --git a/src/lib/tauri-commands.ts b/src/lib/tauri-commands.ts index 6f532a2..41180a5 100644 --- a/src/lib/tauri-commands.ts +++ b/src/lib/tauri-commands.ts @@ -250,6 +250,23 @@ export async function checkAllUpdates(): Promise { return await invoke('check_all_updates'); } +/** + * 更新工具实例(使用配置的安装器路径) + * @param instanceId 工具实例ID + * @param force 是否强制更新 + * @returns 更新结果 + */ +export async function updateToolInstance( + instanceId: string, + force?: boolean, +): Promise { + return await invoke('update_tool_instance', { instanceId, force }); +} + +/** + * 更新工具(旧版本,已废弃) + * @deprecated 请使用 updateToolInstance + */ export async function updateTool(tool: string, force?: boolean): Promise { return await invoke('update_tool', { tool, force }); } diff --git a/src/pages/ToolManagementPage/hooks/useToolManagement.ts b/src/pages/ToolManagementPage/hooks/useToolManagement.ts index e3328b1..aea3774 100644 --- a/src/pages/ToolManagementPage/hooks/useToolManagement.ts +++ b/src/pages/ToolManagementPage/hooks/useToolManagement.ts @@ -7,7 +7,7 @@ import { addSshToolInstance, deleteToolInstance, checkUpdateForInstance, - updateTool, + updateToolInstance, } from '@/lib/tauri-commands'; import type { ToolInstance, SSHConfig } from '@/types/tool-management'; import { useToast } from '@/hooks/use-toast'; @@ -205,7 +205,7 @@ export function useToolManagement() { // 执行更新 const handleUpdate = useCallback( async (instanceId: string) => { - // 从 instance 中解析 baseId + // 从 instance 中解析 baseId(用于显示) const parts = instanceId.split('-'); const typeIndex = parts.findIndex((p) => ['local', 'wsl', 'ssh'].includes(p)); const baseId = typeIndex > 0 ? parts.slice(0, typeIndex).join('-') : parts[0]; @@ -218,7 +218,8 @@ export function useToolManagement() { description: `正在更新 ${baseId}...`, }); - const result = await updateTool(baseId); + // 使用新的基于实例的更新命令 + const result = await updateToolInstance(instanceId); if (result.success) { toast({ From db9002ae468e47c2beba23f8661574aa31c6a924 Mon Sep 17 00:00:00 2001 From: JSRCode <139555610+jsrcode@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:55:44 +0800 Subject: [PATCH 7/7] =?UTF-8?q?refactor(ui):=20=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=AE=9E=E4=BE=8B=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=A1=86=E7=9A=84=E5=AE=89=E8=A3=85=E5=99=A8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改动 **UI 流程重构:** - 将安装器类型选择移至路径验证成功之后(避免用户盲目选择) - 验证路径后智能判断: - 扫描到安装器 → 显示下拉选择器 + "自定义"按钮 - 未扫描到 → 显示安装器类型选择 + 路径输入 - 点击"自定义"按钮可切换到手动配置模式 **状态管理优化:** - 对话框打开时完整重置所有状态(避免脏数据) - 路径输入框 onChange 时清除扫描结果和安装器候选 - 添加 `showCustomInstaller` 状态控制手动配置模式 **用户体验提升:** - 路径验证和安装器扫描合并为单次操作 - 按钮文案优化:"验证中..." → "验证路径" - 自动检测时优先展示扫描结果,减少手动配置步骤 - 自定义模式保留完整的类型选择和路径输入功能 ## 视觉改进 - 验证成功后分阶段展示:路径验证 → 安装器配置 - 安装器候选列表显示级别信息(同级/上级) - 类型选择卡片保持响应式布局(4列网格) ## 影响范围 仅影响前端组件 `AddInstanceDialog`,无后端变更,向后兼容。 --- .../components/AddInstanceDialog.tsx | 250 ++++++++++-------- 1 file changed, 144 insertions(+), 106 deletions(-) diff --git a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx index 491ac9d..db0a3df 100644 --- a/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx +++ b/src/pages/ToolManagementPage/components/AddInstanceDialog.tsx @@ -131,6 +131,27 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro setInstallerCandidates([]); }, [baseId, envType, localMethod]); + // 对话框打开时重置所有状态 + useEffect(() => { + if (open) { + setStep(1); + setBaseId('claude-code'); + setEnvType('local'); + setLocalMethod('auto'); + setManualPath(''); + setInstallMethod('npm'); + setInstallerPath(''); + setInstallerCandidates([]); + setToolCandidates([]); + setSelectedToolCandidate(null); + setShowCustomInstaller(false); + setValidating(false); + setValidationError(null); + setScanResult(null); + // selectedDistro 不重置,因为可能从第一个 useEffect 设置 + } + }, [open]); + const getCommonPaths = () => { const isWindows = navigator.platform.toLowerCase().includes('win'); if (isWindows) { @@ -438,6 +459,8 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro setInstallerCandidates([]); setToolCandidates([]); setSelectedToolCandidate(null); + setShowCustomInstaller(false); + setValidating(false); setValidationError(null); setSelectedDistro(''); setScanResult(null); @@ -698,6 +721,7 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro
+ {/* 工具路径输入 */}
@@ -706,7 +730,9 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro onChange={(e) => { setManualPath(e.target.value); setValidationError(null); - setScanResult(null); // 清除扫描结果 + setScanResult(null); + setInstallerCandidates([]); + setShowCustomInstaller(false); }} onBlur={() => { if (manualPath) handleValidate(manualPath); @@ -735,46 +761,51 @@ export function AddInstanceDialog({ open, onClose, onAdd }: AddInstanceDialogPro {validationError} )} -
- {/* 安装器类型选择 */} -
- -
- {INSTALL_METHODS.map((method) => ( - - ))} -
-
+ {/* 验证路径按钮 */} + - {/* 安装器路径(非 other 时显示) */} - {installMethod !== 'other' && ( -
- + {/* 验证成功提示 */} + {scanResult && scanResult.installed && ( + + + + ✓ 验证成功:{toolNames[baseId]} v{scanResult.version} + + + )} +
- {/* 如果扫描到候选,显示选择列表 */} - {installerCandidates.length > 0 ? ( - <> + {/* 安装器配置(验证成功后显示) */} + {scanResult && scanResult.installed && ( + <> + {/* 情况A:扫描到安装器且未点击自定义 - 显示下拉选择 */} + {installerCandidates.length > 0 && !showCustomInstaller ? ( +
+
+ + +

- 已自动扫描到 {installerCandidates.length} 个安装器,可切换或手动输入 + 已自动扫描到 {installerCandidates.length}{' '} + 个安装器,点击「自定义」可手动配置

- +
) : ( + /* 情况B:点击自定义 或 没扫描到 - 显示安装器类型和路径输入 */ <> -
- setInstallerPath(e.target.value)} - placeholder={`如: ${navigator.platform.toLowerCase().includes('win') ? 'C:\\Program Files\\nodejs\\npm.cmd' : '/usr/local/bin/npm'}`} - disabled={loading || scanning} - /> - +
+ +
+ {INSTALL_METHODS.map((method) => ( + + ))} +
-

- 未检测到安装器,请手动选择或留空(无法快捷更新) -

- - )} -
- )} - {/* Other 类型警告 */} - {installMethod === 'other' && ( - - - - 「其他」类型不支持 APP 内快捷更新 -
- 您需要手动更新此工具 -
-
- )} + {/* 安装器路径输入(非 other 时显示) */} + {installMethod !== 'other' && ( +
+ +
+ setInstallerPath(e.target.value)} + placeholder={`如: ${navigator.platform.toLowerCase().includes('win') ? 'C:\\Program Files\\nodejs\\npm.cmd' : '/usr/local/bin/npm'}`} + disabled={loading || scanning} + /> + +
+

+ {installerCandidates.length === 0 + ? '未检测到安装器,请手动选择或留空(无法快捷更新)' + : '手动指定安装器路径'} +

+
+ )} - {/* 验证路径按钮 */} -
- - - {scanResult && ( - - - - {scanResult.installed ? ( - <> - ✓ 验证成功:{toolNames[baseId]} v{scanResult.version} - - ) : ( - <>验证失败 - )} - - - )} -
+ + )} )}