diff --git a/crates/atlas-embed/Cargo.toml b/crates/atlas-embed/Cargo.toml index be279378..a7ab49c8 100644 --- a/crates/atlas-embed/Cargo.toml +++ b/crates/atlas-embed/Cargo.toml @@ -15,4 +15,11 @@ candle-core = "0.11.0" candle-nn = "0.11.0" candle-transformers = "0.11.0" serde_json = "1.0.150" -tokenizers = "0.23.1" +# `esaxx_fast` (on by default) pulls in `esaxx-rs`'s C++ suffix-array backend, +# whose build.rs hardcodes a static CRT (`.static_crt(true)`) on every +# platform. That conflicts with the rest of the binary's dynamic CRT (notably +# `cxx`, pulled in via `usearch`) and fails MSVC linking with LNK2038 on +# Windows. It only speeds up *training* a Unigram tokenizer (esaxx-rs still +# has a pure-Rust fallback); loading/running pretrained tokenizers is +# unaffected, so it's safe to drop. +tokenizers = { version = "0.23.1", default-features = false, features = ["progressbar", "onig"] } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e3ed69e0..2dcc2d28 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2266,9 +2266,6 @@ name = "esaxx-rs" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" -dependencies = [ - "cc", -] [[package]] name = "event-listener" diff --git a/src-tauri/gen/schemas/windows-schema.json b/src-tauri/gen/schemas/windows-schema.json new file mode 100644 index 00000000..cb077314 --- /dev/null +++ b/src-tauri/gen/schemas/windows-schema.json @@ -0,0 +1,2831 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern 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`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern 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`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures 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-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-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "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. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "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\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`", + "type": "string", + "const": "notification:default", + "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`" + }, + { + "description": "Enables the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-batch", + "markdownDescription": "Enables the batch command without any pre-configured scope." + }, + { + "description": "Enables the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-cancel", + "markdownDescription": "Enables the cancel command without any pre-configured scope." + }, + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-create-channel", + "markdownDescription": "Enables the create_channel command without any pre-configured scope." + }, + { + "description": "Enables the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-delete-channel", + "markdownDescription": "Enables the delete_channel command without any pre-configured scope." + }, + { + "description": "Enables the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-active", + "markdownDescription": "Enables the get_active command without any pre-configured scope." + }, + { + "description": "Enables the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-pending", + "markdownDescription": "Enables the get_pending command without any pre-configured scope." + }, + { + "description": "Enables the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-is-permission-granted", + "markdownDescription": "Enables the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Enables the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-list-channels", + "markdownDescription": "Enables the list_channels command without any pre-configured scope." + }, + { + "description": "Enables the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-notify", + "markdownDescription": "Enables the notify command without any pre-configured scope." + }, + { + "description": "Enables the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-permission-state", + "markdownDescription": "Enables the permission_state command without any pre-configured scope." + }, + { + "description": "Enables the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-action-types", + "markdownDescription": "Enables the register_action_types command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-remove-active", + "markdownDescription": "Enables the remove_active command without any pre-configured scope." + }, + { + "description": "Enables the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-request-permission", + "markdownDescription": "Enables the request_permission command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Denies the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-batch", + "markdownDescription": "Denies the batch command without any pre-configured scope." + }, + { + "description": "Denies the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-cancel", + "markdownDescription": "Denies the cancel command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-create-channel", + "markdownDescription": "Denies the create_channel command without any pre-configured scope." + }, + { + "description": "Denies the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-delete-channel", + "markdownDescription": "Denies the delete_channel command without any pre-configured scope." + }, + { + "description": "Denies the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-active", + "markdownDescription": "Denies the get_active command without any pre-configured scope." + }, + { + "description": "Denies the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-pending", + "markdownDescription": "Denies the get_pending command without any pre-configured scope." + }, + { + "description": "Denies the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-is-permission-granted", + "markdownDescription": "Denies the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Denies the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-list-channels", + "markdownDescription": "Denies the list_channels command without any pre-configured scope." + }, + { + "description": "Denies the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-notify", + "markdownDescription": "Denies the notify command without any pre-configured scope." + }, + { + "description": "Denies the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-permission-state", + "markdownDescription": "Denies the permission_state command without any pre-configured scope." + }, + { + "description": "Denies the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-action-types", + "markdownDescription": "Denies the register_action_types command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-remove-active", + "markdownDescription": "Denies the remove_active command without any pre-configured scope." + }, + { + "description": "Denies the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-request-permission", + "markdownDescription": "Denies the request_permission command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`", + "type": "string", + "const": "window-state:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`" + }, + { + "description": "Enables the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-filename", + "markdownDescription": "Enables the filename command without any pre-configured scope." + }, + { + "description": "Enables the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-restore-state", + "markdownDescription": "Enables the restore_state command without any pre-configured scope." + }, + { + "description": "Enables the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-save-window-state", + "markdownDescription": "Enables the save_window_state command without any pre-configured scope." + }, + { + "description": "Denies the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-filename", + "markdownDescription": "Denies the filename command without any pre-configured scope." + }, + { + "description": "Denies the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-restore-state", + "markdownDescription": "Denies the restore_state command without any pre-configured scope." + }, + { + "description": "Denies the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-save-window-state", + "markdownDescription": "Denies the save_window_state command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/src/commands/browser.rs b/src-tauri/src/commands/browser.rs index 3d4a8522..eae28a10 100644 --- a/src-tauri/src/commands/browser.rs +++ b/src-tauri/src/commands/browser.rs @@ -246,8 +246,10 @@ pub fn browser_embed_create( let id_started = id.clone(); let id_finished = id.clone(); + // `WebviewBuilder` (unlike `WebviewWindowBuilder`) has no `.visible()` + // knob — the child webview is created shown, then hidden immediately + // below to match the "initially hidden" contract callers rely on. let builder = tauri::webview::WebviewBuilder::new(&label, WebviewUrl::External(parsed)) - .visible(false) .data_directory(profile) .on_page_load(move |webview, payload| { let url = payload.url().to_string(); @@ -273,13 +275,14 @@ pub fn browser_embed_create( } }); - window + let webview = window .add_child( builder, Position::Logical(LogicalPosition::new(rect.x, rect.y)), Size::Logical(LogicalSize::new(rect.width.max(1.0), rect.height.max(1.0))), ) .map_err(|e| format!("failed to embed browser webview: {e}"))?; + let _ = webview.hide(); Ok(()) } diff --git a/src-tauri/src/commands/updater.rs b/src-tauri/src/commands/updater.rs index 29438b8b..0bff5f0d 100644 --- a/src-tauri/src/commands/updater.rs +++ b/src-tauri/src/commands/updater.rs @@ -3,860 +3,18 @@ //! //! Atlas ships as an Apple-signed + notarized + stapled `.dmg` (no Tauri-updater //! `.app.tar.gz`/minisign artifact), so we don't use the Tauri updater plugin. -//! Instead: -//! -//! 1. On startup (and every few hours) a non-blocking check queries PostHog -//! remote config for `{version, uri}` ([`check_in_background`]). -//! 2. If newer, the DMG is **downloaded in the background** (resumable) to a -//! staging dir — the app stays fully usable, only a titlebar arc shows. -//! 3. The DMG's Apple signature is verified and the `.app` is unpacked into a -//! pending "staged" location (the running binary is untouched). -//! 4. The user is notified non-blockingly ("Restart to update"). They can -//! **Restart now** (swap + relaunch) or **Later** — in which case the staged -//! update is applied automatically on the next natural quit ([`apply_on_exit`]). -//! -//! Everything is `auto_update`-gated and honors an "ignored version". -//! -//! Events emitted to the frontend: -//! `atlas:update-checking` `{ checking }` -//! `atlas:update-available` `{ version, currentVersion }` (download starting) -//! `atlas:update-progress` `{ version, downloaded, total, phase }` -//! `atlas:update-ready` `{ version }` (staged, restart to apply) -//! `atlas:update-error` `{ message }` -//! `atlas:update-applied` `{ version }` (post-restart toast) - -use std::os::unix::fs::FileExt; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -use std::time::Duration; - -use futures::StreamExt; -use parking_lot::Mutex; -use serde::{Deserialize, Serialize}; -use tauri::{AppHandle, Emitter, Manager, State}; -use tokio::io::AsyncWriteExt; - -/// Concurrent connections used to fetch the DMG. GitHub release assets (S3) -/// throttle per-connection, so a single stream can be very slow (~67 KB/s seen -/// for a 20 MB file); splitting into ranged segments saturates the link. -const DL_CONNECTIONS: u64 = 8; -/// Below this size, parallelism isn't worth the extra requests — stream it. -const DL_PARALLEL_MIN: u64 = 4 * 1024 * 1024; -/// Flush accumulated bytes to disk once a segment buffers this much. -const DL_WRITE_CHUNK: usize = 1024 * 1024; - -use crate::state::{AppState, AppStateHandle}; -use crate::telemetry::{RemoteUpdateConfig, TelemetryClient}; - -/// The running app's version (compile-time). Compared against the remote value. -const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Apple Team ID the downloaded DMG's app MUST be signed by, or we refuse to -/// install it — the security anchor for the whole update, since the DMG is -/// fetched over an attacker-controllable remote-config URL. -const EXPECTED_TEAM_ID: &str = "PLKDA3WBJJ"; - -/// How often to re-check for updates while the app runs. -const RECHECK_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60); - -/// Persisted record of a staged update (`/updates/staging.json`). -#[derive(Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -struct Staging { - version: String, - /// Path to the verified, unpacked `.app` once ready. - staged_app: Option, - /// The DMG has been downloaded, verified, and unpacked — ready to swap. - ready: bool, - /// The swap has been performed (applied on restart/quit) — used at startup - /// to detect a completed update and clean up. - #[serde(default)] - applied: bool, -} - -/// In-memory updater state (managed). -#[derive(Default)] -pub struct UpdaterState { - /// Latest `{version, uri}` from a check. - pending: Mutex>, - /// Guards against concurrent background downloads. - downloading: AtomicBool, - /// Version currently staged + ready (mirror of the on-disk manifest). - ready: Mutex>, -} - -impl UpdaterState { - pub fn new() -> Self { - Self::default() - } -} - -// ── Paths / manifest ───────────────────────────────────────────────────────── - -fn updates_dir(app: &AppHandle) -> Result { - let base = app - .path() - .app_data_dir() - .map_err(|e| format!("app_data_dir: {e}"))?; - Ok(base.join("updates")) -} - -fn manifest_path(app: &AppHandle) -> Result { - Ok(updates_dir(app)?.join("staging.json")) -} - -fn load_manifest(app: &AppHandle) -> Option { - let path = manifest_path(app).ok()?; - let raw = std::fs::read_to_string(path).ok()?; - serde_json::from_str(&raw).ok() -} - -fn save_manifest(app: &AppHandle, m: &Staging) -> Result<(), String> { - let dir = updates_dir(app)?; - std::fs::create_dir_all(&dir).map_err(|e| format!("updates dir: {e}"))?; - let raw = serde_json::to_string_pretty(m).map_err(|e| format!("serialize manifest: {e}"))?; - std::fs::write(dir.join("staging.json"), raw).map_err(|e| format!("write manifest: {e}")) -} - -// ── Helpers ────────────────────────────────────────────────────────────────── - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct UpdateStatus { - pub available: bool, - pub version: Option, - pub current_version: String, -} - -/// UI-hydration snapshot (Settings / titlebar on mount). -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct UpdaterSnapshot { - /// "idle" | "downloading" | "ready" - pub phase: String, - pub version: Option, - pub current_version: String, -} - -/// Semver "is `remote` strictly newer than `current`?". False on parse failure. -fn is_newer(remote: &str, current: &str) -> bool { - match ( - semver::Version::parse(remote.trim()), - semver::Version::parse(current.trim()), - ) { - (Ok(r), Ok(c)) => r > c, - _ => false, - } -} - -fn read_settings(app: &AppHandle) -> (bool, Option) { - let state = app.state::(); - let guard = state.lock(); - ( - guard.settings.auto_update, - guard.settings.updater_ignored_version.clone(), - ) -} - -async fn fetch_remote(app: &AppHandle) -> Option { - let tel = app.state::>().inner().clone(); - tel.fetch_remote_config().await -} - -fn emit_checking(app: &AppHandle, checking: bool) { - let _ = app.emit("atlas:update-checking", serde_json::json!({ "checking": checking })); -} - -fn emit_progress(app: &AppHandle, version: &str, downloaded: u64, total: u64, phase: &str) { - let _ = app.emit( - "atlas:update-progress", - serde_json::json!({ "version": version, "downloaded": downloaded, "total": total, "phase": phase }), - ); -} - -// ── Check ──────────────────────────────────────────────────────────────────── - -/// Non-blocking check. Honors `auto_update` + ignored version. If a newer -/// version is found, kicks off the background download+stage (or re-notifies -/// "ready" if it's already staged). Emits `atlas:update-available`. -pub fn check_in_background(app: &AppHandle) { - let app = app.clone(); - tauri::async_runtime::spawn(async move { - let (auto, ignored) = read_settings(&app); - if !auto { - return; - } - emit_checking(&app, true); - let cfg = fetch_remote(&app).await; - emit_checking(&app, false); - let Some(cfg) = cfg else { return }; - if !is_newer(&cfg.version, CURRENT_VERSION) { - return; - } - if ignored.as_deref() == Some(cfg.version.as_str()) { - return; - } - maybe_start_update(&app, cfg, false).await; - }); -} - -/// Given a newer remote config, either re-notify a matching staged update or -/// start the background download. `force` bypasses the ignored-version gate -/// (used by the manual check). -async fn maybe_start_update(app: &AppHandle, cfg: RemoteUpdateConfig, _force: bool) { - *app.state::().pending.lock() = Some(cfg.clone()); - - // Already downloaded + staged for this exact version → just notify. - if let Some(m) = load_manifest(app) { - if m.ready && m.version == cfg.version { - if let Some(p) = &m.staged_app { - if Path::new(p).exists() { - *app.state::().ready.lock() = Some(cfg.version.clone()); - let _ = app.emit("atlas:update-ready", serde_json::json!({ "version": cfg.version })); - return; - } - } - } - } - - let _ = app.emit( - "atlas:update-available", - serde_json::json!({ "version": cfg.version, "currentVersion": CURRENT_VERSION }), - ); - download_and_stage(app.clone(), cfg).await; -} - -/// Manual "Check for updates" — bypasses the auto_update / ignored gates (an -/// explicit user action). Triggers the background download when newer. -#[tauri::command] -pub async fn update_check_now(app: AppHandle) -> Result { - emit_checking(&app, true); - let cfg = fetch_remote(&app).await; - emit_checking(&app, false); - let available = cfg - .as_ref() - .map(|c| is_newer(&c.version, CURRENT_VERSION)) - .unwrap_or(false); - let version = cfg.as_ref().map(|c| c.version.clone()); - if available { - maybe_start_update(&app, cfg.unwrap(), true).await; - } - Ok(UpdateStatus { - available, - version, - current_version: CURRENT_VERSION.to_string(), - }) -} - -/// Current updater state for UI hydration on mount. -#[tauri::command] -pub fn update_state(app: AppHandle, state: State<'_, UpdaterState>) -> UpdaterSnapshot { - if let Some(v) = state.ready.lock().clone() { - return UpdaterSnapshot { - phase: "ready".into(), - version: Some(v), - current_version: CURRENT_VERSION.to_string(), - }; - } - if state.downloading.load(Ordering::SeqCst) { - let v = state.pending.lock().as_ref().map(|c| c.version.clone()); - return UpdaterSnapshot { - phase: "downloading".into(), - version: v, - current_version: CURRENT_VERSION.to_string(), - }; - } - // Fall back to the on-disk manifest (e.g. staged before this window mounted). - if let Some(m) = load_manifest(&app) { - if m.ready && is_newer(&m.version, CURRENT_VERSION) { - return UpdaterSnapshot { - phase: "ready".into(), - version: Some(m.version), - current_version: CURRENT_VERSION.to_string(), - }; - } - } - UpdaterSnapshot { - phase: "idle".into(), - version: None, - current_version: CURRENT_VERSION.to_string(), - } -} - -/// Persist a "don't prompt for this version again" choice. -#[tauri::command] -pub fn update_ignore( - version: String, - app: AppHandle, - state: State<'_, AppStateHandle>, -) -> Result<(), String> { - let snapshot = { - let mut guard = state.lock(); - guard.settings.updater_ignored_version = Some(version); - guard.clone() - }; - let app2 = app.clone(); - std::thread::spawn(move || { - if let Err(e) = AppState::save(&app2, &snapshot) { - tracing::warn!(target: "atlas::updater", "save ignored version failed: {e}"); - } - }); - Ok(()) -} - -// ── Download + stage ───────────────────────────────────────────────────────── - -/// Orchestrate a background download → verify → stage. Single-flight via the -/// `downloading` guard. On success emits `atlas:update-ready`. -async fn download_and_stage(app: AppHandle, cfg: RemoteUpdateConfig) { - if app - .state::() - .downloading - .swap(true, Ordering::SeqCst) - { - return; // a download is already in flight - } - let result = do_download_and_stage(&app, &cfg).await; - app.state::() - .downloading - .store(false, Ordering::SeqCst); - - match result { - Ok(_) => { - *app.state::().ready.lock() = Some(cfg.version.clone()); - let _ = app.emit("atlas:update-ready", serde_json::json!({ "version": cfg.version })); - } - Err(e) => { - tracing::warn!(target: "atlas::updater", "download/stage failed: {e}"); - let _ = app.emit("atlas:update-error", serde_json::json!({ "message": e })); - } - } -} - -async fn do_download_and_stage(app: &AppHandle, cfg: &RemoteUpdateConfig) -> Result { - let dir = updates_dir(app)?; - std::fs::create_dir_all(&dir).map_err(|e| format!("updates dir: {e}"))?; - let dmg = dir.join(format!("Atlas-{}.dmg", cfg.version)); - let part = dir.join(format!("Atlas-{}.dmg.part", cfg.version)); - - // Already fully staged? short-circuit. - if let Some(m) = load_manifest(app) { - if m.ready && m.version == cfg.version { - if let Some(p) = m.staged_app { - if Path::new(&p).exists() { - return Ok(PathBuf::from(p)); - } - } - } - } - - if !dmg.exists() { - download_to(app, &cfg.uri, &part, &dmg, &cfg.version).await?; - } - - // Mount + verify + unpack (blocking / subprocess heavy). - let appc = app.clone(); - let dmgc = dmg.clone(); - let dirc = dir.clone(); - let ver = cfg.version.clone(); - let staged = tauri::async_runtime::spawn_blocking(move || stage_from_dmg(&appc, &dmgc, &dirc, &ver)) - .await - .map_err(|e| format!("stage join: {e}"))??; - - save_manifest( - app, - &Staging { - version: cfg.version.clone(), - staged_app: Some(staged.to_string_lossy().into_owned()), - ready: true, - applied: false, - }, - )?; - // The DMG is unpacked; free the disk space (keep only the staged .app). - let _ = std::fs::remove_file(&dmg); - Ok(staged) -} - -/// Download the DMG to `part`, then atomically rename to `final_path`. Uses a -/// **parallel multi-connection range download** when the server supports it -/// (fast on throttled CDNs like GitHub/S3); falls back to a single stream. -async fn download_to( - app: &AppHandle, - uri: &str, - part: &Path, - final_path: &Path, - version: &str, -) -> Result<(), String> { - // One pooled client shared by every connection. - let client = reqwest::Client::builder() - .build() - .map_err(|e| format!("http client: {e}"))?; - - // Probe with a 1-byte ranged GET: a 206 + `Content-Range: …/` tells us - // the size AND that range requests work (so we can parallelize). - let (total, ranges_ok) = probe_size(&client, uri).await; - - let _ = std::fs::remove_file(part); - let mut ok = false; - if ranges_ok && total >= DL_PARALLEL_MIN { - match download_parallel(app, &client, uri, part, total, version).await { - Ok(()) => ok = true, - Err(e) => { - // Range handling can misbehave behind some redirects/CDNs; degrade - // to a correct (if slower) single stream rather than fail. - tracing::warn!(target: "atlas::updater", "parallel download failed ({e}); falling back to single stream"); - let _ = std::fs::remove_file(part); - } - } - } - if !ok { - download_stream(app, &client, uri, part, total, version).await?; - } - - std::fs::rename(part, final_path).map_err(|e| { - let _ = std::fs::remove_file(part); - format!("finalize download: {e}") - }) -} - -/// Returns `(total_bytes, range_supported)`. `total = 0` when unknown. -async fn probe_size(client: &reqwest::Client, uri: &str) -> (u64, bool) { - let resp = client - .get(uri) - .header(reqwest::header::RANGE, "bytes=0-0") - .send() - .await; - let Ok(resp) = resp else { return (0, false) }; - if resp.status().as_u16() == 206 { - // Content-Range: "bytes 0-0/12345" - if let Some(total) = resp - .headers() - .get(reqwest::header::CONTENT_RANGE) - .and_then(|v| v.to_str().ok()) - .and_then(|s| s.rsplit('/').next()) - .and_then(|s| s.trim().parse::().ok()) - { - return (total, true); - } - } - // Range not honored — fall back to the full length if advertised. - (resp.content_length().unwrap_or(0), false) -} - -/// Parallel range download: pre-size the file, fetch N byte-ranges concurrently, -/// each writing at its absolute offset. A ticker emits smooth progress. -async fn download_parallel( - app: &AppHandle, - client: &reqwest::Client, - uri: &str, - part: &Path, - total: u64, - version: &str, -) -> Result<(), String> { - let file = std::fs::File::create(part).map_err(|e| format!("create part: {e}"))?; - file.set_len(total).map_err(|e| format!("size part: {e}"))?; - let file = Arc::new(file); - - let downloaded = Arc::new(AtomicU64::new(0)); - let done = Arc::new(AtomicBool::new(false)); - - // Progress ticker — decoupled from the writers so emits stay smooth and - // aren't multiplied by the concurrent connections. - let ticker = { - let app = app.clone(); - let downloaded = downloaded.clone(); - let done = done.clone(); - let version = version.to_string(); - tauri::async_runtime::spawn(async move { - loop { - emit_progress(&app, &version, downloaded.load(Ordering::Relaxed), total, "downloading"); - if done.load(Ordering::Relaxed) { - break; - } - tokio::time::sleep(Duration::from_millis(200)).await; - } - }) - }; - - let seg = total.div_ceil(DL_CONNECTIONS); - let mut handles = Vec::new(); - let mut start = 0u64; - while start < total { - let end = (start + seg).min(total) - 1; - let client = client.clone(); - let uri = uri.to_string(); - let file = file.clone(); - let downloaded = downloaded.clone(); - handles.push(tauri::async_runtime::spawn(async move { - download_segment(&client, &uri, start, end, file, downloaded).await - })); - start += seg; - } - - let mut err: Option = None; - for h in handles { - match h.await { - Ok(Ok(())) => {} - Ok(Err(e)) => err = Some(e), - Err(e) => err = Some(format!("segment join: {e}")), - } - } - done.store(true, Ordering::Relaxed); - let _ = ticker.await; - - if let Some(e) = err { - let _ = std::fs::remove_file(part); - return Err(e); - } - emit_progress(app, version, total, total, "downloading"); - Ok(()) -} - -/// Fetch one byte-range and write it at its absolute offset (positional writes -/// are safe to run concurrently on non-overlapping ranges). -async fn download_segment( - client: &reqwest::Client, - uri: &str, - start: u64, - end: u64, - file: Arc, - downloaded: Arc, -) -> Result<(), String> { - let resp = client - .get(uri) - .header(reqwest::header::RANGE, format!("bytes={start}-{end}")) - .send() - .await - .map_err(|e| format!("segment request: {e}"))?; - // Require a *partial* response — a 200 means the server ignored the Range and - // sent the whole file, which would corrupt this offset-based writer. - if resp.status().as_u16() != 206 { - return Err(format!("segment download not ranged: HTTP {}", resp.status())); - } - - let mut offset = start; - let mut buf: Vec = Vec::with_capacity(DL_WRITE_CHUNK); - let mut stream = resp.bytes_stream(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|e| format!("segment chunk: {e}"))?; - downloaded.fetch_add(chunk.len() as u64, Ordering::Relaxed); - buf.extend_from_slice(&chunk); - if buf.len() >= DL_WRITE_CHUNK { - let data = std::mem::take(&mut buf); - let at = offset; - offset += data.len() as u64; - let f = file.clone(); - tokio::task::spawn_blocking(move || f.write_all_at(&data, at)) - .await - .map_err(|e| format!("write join: {e}"))? - .map_err(|e| format!("write segment: {e}"))?; - } - } - if !buf.is_empty() { - let at = offset; - tokio::task::spawn_blocking(move || file.write_all_at(&buf, at)) - .await - .map_err(|e| format!("write join: {e}"))? - .map_err(|e| format!("write segment: {e}"))?; - } - Ok(()) -} - -/// Single-connection fallback (no range support / small file). -async fn download_stream( - app: &AppHandle, - client: &reqwest::Client, - uri: &str, - part: &Path, - total: u64, - version: &str, -) -> Result<(), String> { - let resp = client.get(uri).send().await.map_err(|e| format!("download: {e}"))?; - if !resp.status().is_success() { - return Err(format!("download failed: HTTP {}", resp.status())); - } - let total = if total > 0 { total } else { resp.content_length().unwrap_or(0) }; - let mut file = tokio::fs::File::create(part) - .await - .map_err(|e| format!("create part: {e}"))?; - let mut downloaded = 0u64; - let mut last_emit = 0u64; - emit_progress(app, version, 0, total, "downloading"); - let mut stream = resp.bytes_stream(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|e| format!("download chunk: {e}"))?; - file.write_all(&chunk).await.map_err(|e| format!("write: {e}"))?; - downloaded += chunk.len() as u64; - if downloaded - last_emit >= DL_WRITE_CHUNK as u64 || (total > 0 && downloaded >= total) { - last_emit = downloaded; - emit_progress(app, version, downloaded, total, "downloading"); - } - } - file.flush().await.map_err(|e| format!("flush: {e}"))?; - Ok(()) -} - -/// Mount the DMG, verify its Apple signature, and unpack the `.app` into -/// `/staged/Atlas.app`. Returns the staged `.app` path. -fn stage_from_dmg(app: &AppHandle, dmg: &Path, dir: &Path, version: &str) -> Result { - emit_progress(app, version, 0, 0, "verifying"); - let mount_point = dir.join("mnt"); - let _ = std::fs::remove_dir_all(&mount_point); - std::fs::create_dir_all(&mount_point).map_err(|e| format!("mount dir: {e}"))?; - - let out = Command::new("hdiutil") - .args(["attach", "-nobrowse", "-readonly", "-mountpoint"]) - .arg(&mount_point) - .arg(dmg) - .output() - .map_err(|e| format!("hdiutil attach: {e}"))?; - if !out.status.success() { - return Err(format!( - "failed to mount update DMG: {}", - String::from_utf8_lossy(&out.stderr).trim() - )); - } - - let result = stage_from_mount(&mount_point, dir); - - let _ = Command::new("hdiutil") - .args(["detach", "-quiet"]) - .arg(&mount_point) - .output(); - let _ = std::fs::remove_dir_all(&mount_point); - result -} - -fn stage_from_mount(mount_point: &Path, dir: &Path) -> Result { - let src_app = std::fs::read_dir(mount_point) - .map_err(|e| format!("read mount: {e}"))? - .filter_map(|e| e.ok()) - .map(|e| e.path()) - .find(|p| p.extension().map(|x| x == "app").unwrap_or(false)) - .ok_or_else(|| "no .app found in the update DMG".to_string())?; - - // Verify the Apple signature + team id BEFORE trusting the payload. - verify_signature(&src_app)?; - - let staged_dir = dir.join("staged"); - let _ = std::fs::remove_dir_all(&staged_dir); - std::fs::create_dir_all(&staged_dir).map_err(|e| format!("staged dir: {e}"))?; - let staged_app = staged_dir.join("Atlas.app"); - - let out = Command::new("ditto") - .arg(&src_app) - .arg(&staged_app) - .output() - .map_err(|e| format!("ditto: {e}"))?; - if !out.status.success() { - return Err(format!( - "failed to unpack update: {}", - String::from_utf8_lossy(&out.stderr).trim() - )); - } - Ok(staged_app) -} - -// ── Apply (swap) ───────────────────────────────────────────────────────────── - -/// "Restart now": swap the staged `.app` over the running install and relaunch. -#[tauri::command] -pub async fn update_apply(app: AppHandle) -> Result<(), String> { - let m = load_manifest(&app).ok_or("no update is staged")?; - if !m.ready { - return Err("update not ready yet".into()); - } - let staged = m.staged_app.clone().ok_or("no staged app")?; - let staged_path = PathBuf::from(staged); - if !staged_path.exists() { - return Err("staged update is missing".into()); - } - let dest = current_app_bundle()?; - - let sp = staged_path.clone(); - let res = tauri::async_runtime::spawn_blocking(move || swap_app(&sp, &dest)) - .await - .map_err(|e| format!("apply join: {e}"))?; - - match res { - Ok(()) => { - let _ = save_manifest( - &app, - &Staging { - version: m.version, - staged_app: None, - ready: false, - applied: true, - }, - ); - if let Ok(dir) = updates_dir(&app) { - let _ = std::fs::remove_dir_all(dir.join("staged")); - } - app.restart(); - } - Err(e) => { - let _ = app.emit("atlas:update-error", serde_json::json!({ "message": e })); - Err(e) - } - } -} - -/// Apply a staged update at natural quit ("Later"). Best-effort, blocking, no -/// relaunch — the next launch is the new version. Skipped for ignored versions. -pub fn apply_on_exit(app: &AppHandle) { - let Some(m) = load_manifest(app) else { return }; - if !m.ready { - return; - } - if !is_newer(&m.version, CURRENT_VERSION) { - return; - } - let ignored = app - .state::() - .lock() - .settings - .updater_ignored_version - .clone(); - if ignored.as_deref() == Some(m.version.as_str()) { - return; - } - let Some(staged) = m.staged_app.clone() else { return }; - let staged_path = PathBuf::from(staged); - if !staged_path.exists() { - return; - } - let Ok(dest) = current_app_bundle() else { return }; - if swap_app(&staged_path, &dest).is_ok() { - let _ = save_manifest( - app, - &Staging { - version: m.version, - staged_app: None, - ready: false, - applied: true, - }, - ); - if let Ok(dir) = updates_dir(app) { - let _ = std::fs::remove_dir_all(dir.join("staged")); - } - } -} - -/// Startup housekeeping: if a staged update was applied (we're now running a -/// version >= the staged one), clean it up and toast. Call before the first -/// check. -pub fn init_on_startup(app: &AppHandle) { - let Some(m) = load_manifest(app) else { return }; - // Running a version at or beyond the staged one → the staging is obsolete - // (applied on the previous quit, or superseded by a manual install). - if !is_newer(&m.version, CURRENT_VERSION) { - if let Ok(dir) = updates_dir(app) { - let _ = std::fs::remove_dir_all(&dir); - } - if m.applied { - let _ = app.emit("atlas:update-applied", serde_json::json!({ "version": CURRENT_VERSION })); - } - } -} - -/// Periodically re-check for updates while the app runs. -pub fn spawn_periodic(app: &AppHandle) { - let app = app.clone(); - tauri::async_runtime::spawn(async move { - let mut interval = tokio::time::interval(RECHECK_INTERVAL); - interval.tick().await; // consume the immediate first tick (startup already checked) - loop { - interval.tick().await; - check_in_background(&app); - } - }); -} - -/// Swap `staged` over `dest` with a `.bak` rollback. Tries an atomic rename -/// (same APFS volume — instant); falls back to a `ditto` copy. -fn swap_app(staged: &Path, dest: &Path) -> Result<(), String> { - let backup = dest.with_extension("app.bak"); - let _ = std::fs::remove_dir_all(&backup); - if dest.exists() { - std::fs::rename(dest, &backup).map_err(|e| format!("back up current app: {e}"))?; - } - // Fast path: atomic directory rename on the same volume. - if std::fs::rename(staged, dest).is_err() { - let out = Command::new("ditto") - .arg(staged) - .arg(dest) - .output() - .map_err(|e| format!("ditto: {e}"))?; - if !out.status.success() { - // Roll back to the original bundle. - let _ = std::fs::remove_dir_all(dest); - if backup.exists() { - let _ = std::fs::rename(&backup, dest); - } - return Err(format!( - "install failed: {}", - String::from_utf8_lossy(&out.stderr).trim() - )); - } - } - let _ = std::fs::remove_dir_all(&backup); - Ok(()) -} - -/// `codesign --verify --deep --strict` + Team ID match + `spctl` Gatekeeper -/// assessment. All three must pass. -fn verify_signature(app_path: &Path) -> Result<(), String> { - let verify = Command::new("codesign") - .args(["--verify", "--deep", "--strict", "--verbose=2"]) - .arg(app_path) - .output() - .map_err(|e| format!("codesign: {e}"))?; - if !verify.status.success() { - return Err("update rejected: code signature invalid".into()); - } +//! That flow ([`updater_macos`]) only makes sense on macOS — mounting a DMG, +//! `codesign`/`spctl` verification, and swapping an `.app` bundle have no +//! Windows/Linux equivalent — so it's compiled in only there. Every other +//! platform gets [`updater_stub`], a no-op with the same public API, so +//! `lib.rs` and the frontend's `invoke()` calls stay platform-agnostic. - let info = Command::new("codesign") - .args(["-dvvv"]) - .arg(app_path) - .output() - .map_err(|e| format!("codesign -dvvv: {e}"))?; - let meta = String::from_utf8_lossy(&info.stderr); - let team_ok = meta - .lines() - .any(|l| l.trim() == format!("TeamIdentifier={EXPECTED_TEAM_ID}")); - if !team_ok { - return Err("update rejected: unexpected signing team".into()); - } +#[cfg(target_os = "macos")] +#[path = "updater_macos.rs"] +mod imp; - let spctl = Command::new("spctl") - .args(["--assess", "--type", "execute", "--verbose=2"]) - .arg(app_path) - .output() - .map_err(|e| format!("spctl: {e}"))?; - if !spctl.status.success() { - return Err("update rejected: notarization check failed".into()); - } - Ok(()) -} +#[cfg(not(target_os = "macos"))] +#[path = "updater_stub.rs"] +mod imp; -/// Resolve the running `Atlas.app` bundle root from the executable path -/// (`…/Atlas.app/Contents/MacOS/atlas` → `…/Atlas.app`). -fn current_app_bundle() -> Result { - let exe = std::env::current_exe().map_err(|e| format!("current_exe: {e}"))?; - let app = exe - .parent() // MacOS - .and_then(|p| p.parent()) // Contents - .and_then(|p| p.parent()) // Atlas.app - .map(|p| p.to_path_buf()) - .ok_or_else(|| "could not resolve app bundle path".to_string())?; - if app.extension().map(|x| x == "app").unwrap_or(false) { - Ok(app) - } else { - Err(format!( - "running from a non-.app location ({}); update via the DMG", - app.display() - )) - } -} +pub use imp::*; diff --git a/src-tauri/src/commands/updater_macos.rs b/src-tauri/src/commands/updater_macos.rs new file mode 100644 index 00000000..29438b8b --- /dev/null +++ b/src-tauri/src/commands/updater_macos.rs @@ -0,0 +1,862 @@ +//! In-app auto-updater (macOS DMG) — Figma/VSCode/Zed-style **background staged** +//! updates. +//! +//! Atlas ships as an Apple-signed + notarized + stapled `.dmg` (no Tauri-updater +//! `.app.tar.gz`/minisign artifact), so we don't use the Tauri updater plugin. +//! Instead: +//! +//! 1. On startup (and every few hours) a non-blocking check queries PostHog +//! remote config for `{version, uri}` ([`check_in_background`]). +//! 2. If newer, the DMG is **downloaded in the background** (resumable) to a +//! staging dir — the app stays fully usable, only a titlebar arc shows. +//! 3. The DMG's Apple signature is verified and the `.app` is unpacked into a +//! pending "staged" location (the running binary is untouched). +//! 4. The user is notified non-blockingly ("Restart to update"). They can +//! **Restart now** (swap + relaunch) or **Later** — in which case the staged +//! update is applied automatically on the next natural quit ([`apply_on_exit`]). +//! +//! Everything is `auto_update`-gated and honors an "ignored version". +//! +//! Events emitted to the frontend: +//! `atlas:update-checking` `{ checking }` +//! `atlas:update-available` `{ version, currentVersion }` (download starting) +//! `atlas:update-progress` `{ version, downloaded, total, phase }` +//! `atlas:update-ready` `{ version }` (staged, restart to apply) +//! `atlas:update-error` `{ message }` +//! `atlas:update-applied` `{ version }` (post-restart toast) + +use std::os::unix::fs::FileExt; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use futures::StreamExt; +use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter, Manager, State}; +use tokio::io::AsyncWriteExt; + +/// Concurrent connections used to fetch the DMG. GitHub release assets (S3) +/// throttle per-connection, so a single stream can be very slow (~67 KB/s seen +/// for a 20 MB file); splitting into ranged segments saturates the link. +const DL_CONNECTIONS: u64 = 8; +/// Below this size, parallelism isn't worth the extra requests — stream it. +const DL_PARALLEL_MIN: u64 = 4 * 1024 * 1024; +/// Flush accumulated bytes to disk once a segment buffers this much. +const DL_WRITE_CHUNK: usize = 1024 * 1024; + +use crate::state::{AppState, AppStateHandle}; +use crate::telemetry::{RemoteUpdateConfig, TelemetryClient}; + +/// The running app's version (compile-time). Compared against the remote value. +const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Apple Team ID the downloaded DMG's app MUST be signed by, or we refuse to +/// install it — the security anchor for the whole update, since the DMG is +/// fetched over an attacker-controllable remote-config URL. +const EXPECTED_TEAM_ID: &str = "PLKDA3WBJJ"; + +/// How often to re-check for updates while the app runs. +const RECHECK_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60); + +/// Persisted record of a staged update (`/updates/staging.json`). +#[derive(Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "camelCase")] +struct Staging { + version: String, + /// Path to the verified, unpacked `.app` once ready. + staged_app: Option, + /// The DMG has been downloaded, verified, and unpacked — ready to swap. + ready: bool, + /// The swap has been performed (applied on restart/quit) — used at startup + /// to detect a completed update and clean up. + #[serde(default)] + applied: bool, +} + +/// In-memory updater state (managed). +#[derive(Default)] +pub struct UpdaterState { + /// Latest `{version, uri}` from a check. + pending: Mutex>, + /// Guards against concurrent background downloads. + downloading: AtomicBool, + /// Version currently staged + ready (mirror of the on-disk manifest). + ready: Mutex>, +} + +impl UpdaterState { + pub fn new() -> Self { + Self::default() + } +} + +// ── Paths / manifest ───────────────────────────────────────────────────────── + +fn updates_dir(app: &AppHandle) -> Result { + let base = app + .path() + .app_data_dir() + .map_err(|e| format!("app_data_dir: {e}"))?; + Ok(base.join("updates")) +} + +fn manifest_path(app: &AppHandle) -> Result { + Ok(updates_dir(app)?.join("staging.json")) +} + +fn load_manifest(app: &AppHandle) -> Option { + let path = manifest_path(app).ok()?; + let raw = std::fs::read_to_string(path).ok()?; + serde_json::from_str(&raw).ok() +} + +fn save_manifest(app: &AppHandle, m: &Staging) -> Result<(), String> { + let dir = updates_dir(app)?; + std::fs::create_dir_all(&dir).map_err(|e| format!("updates dir: {e}"))?; + let raw = serde_json::to_string_pretty(m).map_err(|e| format!("serialize manifest: {e}"))?; + std::fs::write(dir.join("staging.json"), raw).map_err(|e| format!("write manifest: {e}")) +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdateStatus { + pub available: bool, + pub version: Option, + pub current_version: String, +} + +/// UI-hydration snapshot (Settings / titlebar on mount). +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdaterSnapshot { + /// "idle" | "downloading" | "ready" + pub phase: String, + pub version: Option, + pub current_version: String, +} + +/// Semver "is `remote` strictly newer than `current`?". False on parse failure. +fn is_newer(remote: &str, current: &str) -> bool { + match ( + semver::Version::parse(remote.trim()), + semver::Version::parse(current.trim()), + ) { + (Ok(r), Ok(c)) => r > c, + _ => false, + } +} + +fn read_settings(app: &AppHandle) -> (bool, Option) { + let state = app.state::(); + let guard = state.lock(); + ( + guard.settings.auto_update, + guard.settings.updater_ignored_version.clone(), + ) +} + +async fn fetch_remote(app: &AppHandle) -> Option { + let tel = app.state::>().inner().clone(); + tel.fetch_remote_config().await +} + +fn emit_checking(app: &AppHandle, checking: bool) { + let _ = app.emit("atlas:update-checking", serde_json::json!({ "checking": checking })); +} + +fn emit_progress(app: &AppHandle, version: &str, downloaded: u64, total: u64, phase: &str) { + let _ = app.emit( + "atlas:update-progress", + serde_json::json!({ "version": version, "downloaded": downloaded, "total": total, "phase": phase }), + ); +} + +// ── Check ──────────────────────────────────────────────────────────────────── + +/// Non-blocking check. Honors `auto_update` + ignored version. If a newer +/// version is found, kicks off the background download+stage (or re-notifies +/// "ready" if it's already staged). Emits `atlas:update-available`. +pub fn check_in_background(app: &AppHandle) { + let app = app.clone(); + tauri::async_runtime::spawn(async move { + let (auto, ignored) = read_settings(&app); + if !auto { + return; + } + emit_checking(&app, true); + let cfg = fetch_remote(&app).await; + emit_checking(&app, false); + let Some(cfg) = cfg else { return }; + if !is_newer(&cfg.version, CURRENT_VERSION) { + return; + } + if ignored.as_deref() == Some(cfg.version.as_str()) { + return; + } + maybe_start_update(&app, cfg, false).await; + }); +} + +/// Given a newer remote config, either re-notify a matching staged update or +/// start the background download. `force` bypasses the ignored-version gate +/// (used by the manual check). +async fn maybe_start_update(app: &AppHandle, cfg: RemoteUpdateConfig, _force: bool) { + *app.state::().pending.lock() = Some(cfg.clone()); + + // Already downloaded + staged for this exact version → just notify. + if let Some(m) = load_manifest(app) { + if m.ready && m.version == cfg.version { + if let Some(p) = &m.staged_app { + if Path::new(p).exists() { + *app.state::().ready.lock() = Some(cfg.version.clone()); + let _ = app.emit("atlas:update-ready", serde_json::json!({ "version": cfg.version })); + return; + } + } + } + } + + let _ = app.emit( + "atlas:update-available", + serde_json::json!({ "version": cfg.version, "currentVersion": CURRENT_VERSION }), + ); + download_and_stage(app.clone(), cfg).await; +} + +/// Manual "Check for updates" — bypasses the auto_update / ignored gates (an +/// explicit user action). Triggers the background download when newer. +#[tauri::command] +pub async fn update_check_now(app: AppHandle) -> Result { + emit_checking(&app, true); + let cfg = fetch_remote(&app).await; + emit_checking(&app, false); + let available = cfg + .as_ref() + .map(|c| is_newer(&c.version, CURRENT_VERSION)) + .unwrap_or(false); + let version = cfg.as_ref().map(|c| c.version.clone()); + if available { + maybe_start_update(&app, cfg.unwrap(), true).await; + } + Ok(UpdateStatus { + available, + version, + current_version: CURRENT_VERSION.to_string(), + }) +} + +/// Current updater state for UI hydration on mount. +#[tauri::command] +pub fn update_state(app: AppHandle, state: State<'_, UpdaterState>) -> UpdaterSnapshot { + if let Some(v) = state.ready.lock().clone() { + return UpdaterSnapshot { + phase: "ready".into(), + version: Some(v), + current_version: CURRENT_VERSION.to_string(), + }; + } + if state.downloading.load(Ordering::SeqCst) { + let v = state.pending.lock().as_ref().map(|c| c.version.clone()); + return UpdaterSnapshot { + phase: "downloading".into(), + version: v, + current_version: CURRENT_VERSION.to_string(), + }; + } + // Fall back to the on-disk manifest (e.g. staged before this window mounted). + if let Some(m) = load_manifest(&app) { + if m.ready && is_newer(&m.version, CURRENT_VERSION) { + return UpdaterSnapshot { + phase: "ready".into(), + version: Some(m.version), + current_version: CURRENT_VERSION.to_string(), + }; + } + } + UpdaterSnapshot { + phase: "idle".into(), + version: None, + current_version: CURRENT_VERSION.to_string(), + } +} + +/// Persist a "don't prompt for this version again" choice. +#[tauri::command] +pub fn update_ignore( + version: String, + app: AppHandle, + state: State<'_, AppStateHandle>, +) -> Result<(), String> { + let snapshot = { + let mut guard = state.lock(); + guard.settings.updater_ignored_version = Some(version); + guard.clone() + }; + let app2 = app.clone(); + std::thread::spawn(move || { + if let Err(e) = AppState::save(&app2, &snapshot) { + tracing::warn!(target: "atlas::updater", "save ignored version failed: {e}"); + } + }); + Ok(()) +} + +// ── Download + stage ───────────────────────────────────────────────────────── + +/// Orchestrate a background download → verify → stage. Single-flight via the +/// `downloading` guard. On success emits `atlas:update-ready`. +async fn download_and_stage(app: AppHandle, cfg: RemoteUpdateConfig) { + if app + .state::() + .downloading + .swap(true, Ordering::SeqCst) + { + return; // a download is already in flight + } + let result = do_download_and_stage(&app, &cfg).await; + app.state::() + .downloading + .store(false, Ordering::SeqCst); + + match result { + Ok(_) => { + *app.state::().ready.lock() = Some(cfg.version.clone()); + let _ = app.emit("atlas:update-ready", serde_json::json!({ "version": cfg.version })); + } + Err(e) => { + tracing::warn!(target: "atlas::updater", "download/stage failed: {e}"); + let _ = app.emit("atlas:update-error", serde_json::json!({ "message": e })); + } + } +} + +async fn do_download_and_stage(app: &AppHandle, cfg: &RemoteUpdateConfig) -> Result { + let dir = updates_dir(app)?; + std::fs::create_dir_all(&dir).map_err(|e| format!("updates dir: {e}"))?; + let dmg = dir.join(format!("Atlas-{}.dmg", cfg.version)); + let part = dir.join(format!("Atlas-{}.dmg.part", cfg.version)); + + // Already fully staged? short-circuit. + if let Some(m) = load_manifest(app) { + if m.ready && m.version == cfg.version { + if let Some(p) = m.staged_app { + if Path::new(&p).exists() { + return Ok(PathBuf::from(p)); + } + } + } + } + + if !dmg.exists() { + download_to(app, &cfg.uri, &part, &dmg, &cfg.version).await?; + } + + // Mount + verify + unpack (blocking / subprocess heavy). + let appc = app.clone(); + let dmgc = dmg.clone(); + let dirc = dir.clone(); + let ver = cfg.version.clone(); + let staged = tauri::async_runtime::spawn_blocking(move || stage_from_dmg(&appc, &dmgc, &dirc, &ver)) + .await + .map_err(|e| format!("stage join: {e}"))??; + + save_manifest( + app, + &Staging { + version: cfg.version.clone(), + staged_app: Some(staged.to_string_lossy().into_owned()), + ready: true, + applied: false, + }, + )?; + // The DMG is unpacked; free the disk space (keep only the staged .app). + let _ = std::fs::remove_file(&dmg); + Ok(staged) +} + +/// Download the DMG to `part`, then atomically rename to `final_path`. Uses a +/// **parallel multi-connection range download** when the server supports it +/// (fast on throttled CDNs like GitHub/S3); falls back to a single stream. +async fn download_to( + app: &AppHandle, + uri: &str, + part: &Path, + final_path: &Path, + version: &str, +) -> Result<(), String> { + // One pooled client shared by every connection. + let client = reqwest::Client::builder() + .build() + .map_err(|e| format!("http client: {e}"))?; + + // Probe with a 1-byte ranged GET: a 206 + `Content-Range: …/` tells us + // the size AND that range requests work (so we can parallelize). + let (total, ranges_ok) = probe_size(&client, uri).await; + + let _ = std::fs::remove_file(part); + let mut ok = false; + if ranges_ok && total >= DL_PARALLEL_MIN { + match download_parallel(app, &client, uri, part, total, version).await { + Ok(()) => ok = true, + Err(e) => { + // Range handling can misbehave behind some redirects/CDNs; degrade + // to a correct (if slower) single stream rather than fail. + tracing::warn!(target: "atlas::updater", "parallel download failed ({e}); falling back to single stream"); + let _ = std::fs::remove_file(part); + } + } + } + if !ok { + download_stream(app, &client, uri, part, total, version).await?; + } + + std::fs::rename(part, final_path).map_err(|e| { + let _ = std::fs::remove_file(part); + format!("finalize download: {e}") + }) +} + +/// Returns `(total_bytes, range_supported)`. `total = 0` when unknown. +async fn probe_size(client: &reqwest::Client, uri: &str) -> (u64, bool) { + let resp = client + .get(uri) + .header(reqwest::header::RANGE, "bytes=0-0") + .send() + .await; + let Ok(resp) = resp else { return (0, false) }; + if resp.status().as_u16() == 206 { + // Content-Range: "bytes 0-0/12345" + if let Some(total) = resp + .headers() + .get(reqwest::header::CONTENT_RANGE) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.rsplit('/').next()) + .and_then(|s| s.trim().parse::().ok()) + { + return (total, true); + } + } + // Range not honored — fall back to the full length if advertised. + (resp.content_length().unwrap_or(0), false) +} + +/// Parallel range download: pre-size the file, fetch N byte-ranges concurrently, +/// each writing at its absolute offset. A ticker emits smooth progress. +async fn download_parallel( + app: &AppHandle, + client: &reqwest::Client, + uri: &str, + part: &Path, + total: u64, + version: &str, +) -> Result<(), String> { + let file = std::fs::File::create(part).map_err(|e| format!("create part: {e}"))?; + file.set_len(total).map_err(|e| format!("size part: {e}"))?; + let file = Arc::new(file); + + let downloaded = Arc::new(AtomicU64::new(0)); + let done = Arc::new(AtomicBool::new(false)); + + // Progress ticker — decoupled from the writers so emits stay smooth and + // aren't multiplied by the concurrent connections. + let ticker = { + let app = app.clone(); + let downloaded = downloaded.clone(); + let done = done.clone(); + let version = version.to_string(); + tauri::async_runtime::spawn(async move { + loop { + emit_progress(&app, &version, downloaded.load(Ordering::Relaxed), total, "downloading"); + if done.load(Ordering::Relaxed) { + break; + } + tokio::time::sleep(Duration::from_millis(200)).await; + } + }) + }; + + let seg = total.div_ceil(DL_CONNECTIONS); + let mut handles = Vec::new(); + let mut start = 0u64; + while start < total { + let end = (start + seg).min(total) - 1; + let client = client.clone(); + let uri = uri.to_string(); + let file = file.clone(); + let downloaded = downloaded.clone(); + handles.push(tauri::async_runtime::spawn(async move { + download_segment(&client, &uri, start, end, file, downloaded).await + })); + start += seg; + } + + let mut err: Option = None; + for h in handles { + match h.await { + Ok(Ok(())) => {} + Ok(Err(e)) => err = Some(e), + Err(e) => err = Some(format!("segment join: {e}")), + } + } + done.store(true, Ordering::Relaxed); + let _ = ticker.await; + + if let Some(e) = err { + let _ = std::fs::remove_file(part); + return Err(e); + } + emit_progress(app, version, total, total, "downloading"); + Ok(()) +} + +/// Fetch one byte-range and write it at its absolute offset (positional writes +/// are safe to run concurrently on non-overlapping ranges). +async fn download_segment( + client: &reqwest::Client, + uri: &str, + start: u64, + end: u64, + file: Arc, + downloaded: Arc, +) -> Result<(), String> { + let resp = client + .get(uri) + .header(reqwest::header::RANGE, format!("bytes={start}-{end}")) + .send() + .await + .map_err(|e| format!("segment request: {e}"))?; + // Require a *partial* response — a 200 means the server ignored the Range and + // sent the whole file, which would corrupt this offset-based writer. + if resp.status().as_u16() != 206 { + return Err(format!("segment download not ranged: HTTP {}", resp.status())); + } + + let mut offset = start; + let mut buf: Vec = Vec::with_capacity(DL_WRITE_CHUNK); + let mut stream = resp.bytes_stream(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|e| format!("segment chunk: {e}"))?; + downloaded.fetch_add(chunk.len() as u64, Ordering::Relaxed); + buf.extend_from_slice(&chunk); + if buf.len() >= DL_WRITE_CHUNK { + let data = std::mem::take(&mut buf); + let at = offset; + offset += data.len() as u64; + let f = file.clone(); + tokio::task::spawn_blocking(move || f.write_all_at(&data, at)) + .await + .map_err(|e| format!("write join: {e}"))? + .map_err(|e| format!("write segment: {e}"))?; + } + } + if !buf.is_empty() { + let at = offset; + tokio::task::spawn_blocking(move || file.write_all_at(&buf, at)) + .await + .map_err(|e| format!("write join: {e}"))? + .map_err(|e| format!("write segment: {e}"))?; + } + Ok(()) +} + +/// Single-connection fallback (no range support / small file). +async fn download_stream( + app: &AppHandle, + client: &reqwest::Client, + uri: &str, + part: &Path, + total: u64, + version: &str, +) -> Result<(), String> { + let resp = client.get(uri).send().await.map_err(|e| format!("download: {e}"))?; + if !resp.status().is_success() { + return Err(format!("download failed: HTTP {}", resp.status())); + } + let total = if total > 0 { total } else { resp.content_length().unwrap_or(0) }; + let mut file = tokio::fs::File::create(part) + .await + .map_err(|e| format!("create part: {e}"))?; + let mut downloaded = 0u64; + let mut last_emit = 0u64; + emit_progress(app, version, 0, total, "downloading"); + let mut stream = resp.bytes_stream(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|e| format!("download chunk: {e}"))?; + file.write_all(&chunk).await.map_err(|e| format!("write: {e}"))?; + downloaded += chunk.len() as u64; + if downloaded - last_emit >= DL_WRITE_CHUNK as u64 || (total > 0 && downloaded >= total) { + last_emit = downloaded; + emit_progress(app, version, downloaded, total, "downloading"); + } + } + file.flush().await.map_err(|e| format!("flush: {e}"))?; + Ok(()) +} + +/// Mount the DMG, verify its Apple signature, and unpack the `.app` into +/// `/staged/Atlas.app`. Returns the staged `.app` path. +fn stage_from_dmg(app: &AppHandle, dmg: &Path, dir: &Path, version: &str) -> Result { + emit_progress(app, version, 0, 0, "verifying"); + let mount_point = dir.join("mnt"); + let _ = std::fs::remove_dir_all(&mount_point); + std::fs::create_dir_all(&mount_point).map_err(|e| format!("mount dir: {e}"))?; + + let out = Command::new("hdiutil") + .args(["attach", "-nobrowse", "-readonly", "-mountpoint"]) + .arg(&mount_point) + .arg(dmg) + .output() + .map_err(|e| format!("hdiutil attach: {e}"))?; + if !out.status.success() { + return Err(format!( + "failed to mount update DMG: {}", + String::from_utf8_lossy(&out.stderr).trim() + )); + } + + let result = stage_from_mount(&mount_point, dir); + + let _ = Command::new("hdiutil") + .args(["detach", "-quiet"]) + .arg(&mount_point) + .output(); + let _ = std::fs::remove_dir_all(&mount_point); + result +} + +fn stage_from_mount(mount_point: &Path, dir: &Path) -> Result { + let src_app = std::fs::read_dir(mount_point) + .map_err(|e| format!("read mount: {e}"))? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .find(|p| p.extension().map(|x| x == "app").unwrap_or(false)) + .ok_or_else(|| "no .app found in the update DMG".to_string())?; + + // Verify the Apple signature + team id BEFORE trusting the payload. + verify_signature(&src_app)?; + + let staged_dir = dir.join("staged"); + let _ = std::fs::remove_dir_all(&staged_dir); + std::fs::create_dir_all(&staged_dir).map_err(|e| format!("staged dir: {e}"))?; + let staged_app = staged_dir.join("Atlas.app"); + + let out = Command::new("ditto") + .arg(&src_app) + .arg(&staged_app) + .output() + .map_err(|e| format!("ditto: {e}"))?; + if !out.status.success() { + return Err(format!( + "failed to unpack update: {}", + String::from_utf8_lossy(&out.stderr).trim() + )); + } + Ok(staged_app) +} + +// ── Apply (swap) ───────────────────────────────────────────────────────────── + +/// "Restart now": swap the staged `.app` over the running install and relaunch. +#[tauri::command] +pub async fn update_apply(app: AppHandle) -> Result<(), String> { + let m = load_manifest(&app).ok_or("no update is staged")?; + if !m.ready { + return Err("update not ready yet".into()); + } + let staged = m.staged_app.clone().ok_or("no staged app")?; + let staged_path = PathBuf::from(staged); + if !staged_path.exists() { + return Err("staged update is missing".into()); + } + let dest = current_app_bundle()?; + + let sp = staged_path.clone(); + let res = tauri::async_runtime::spawn_blocking(move || swap_app(&sp, &dest)) + .await + .map_err(|e| format!("apply join: {e}"))?; + + match res { + Ok(()) => { + let _ = save_manifest( + &app, + &Staging { + version: m.version, + staged_app: None, + ready: false, + applied: true, + }, + ); + if let Ok(dir) = updates_dir(&app) { + let _ = std::fs::remove_dir_all(dir.join("staged")); + } + app.restart(); + } + Err(e) => { + let _ = app.emit("atlas:update-error", serde_json::json!({ "message": e })); + Err(e) + } + } +} + +/// Apply a staged update at natural quit ("Later"). Best-effort, blocking, no +/// relaunch — the next launch is the new version. Skipped for ignored versions. +pub fn apply_on_exit(app: &AppHandle) { + let Some(m) = load_manifest(app) else { return }; + if !m.ready { + return; + } + if !is_newer(&m.version, CURRENT_VERSION) { + return; + } + let ignored = app + .state::() + .lock() + .settings + .updater_ignored_version + .clone(); + if ignored.as_deref() == Some(m.version.as_str()) { + return; + } + let Some(staged) = m.staged_app.clone() else { return }; + let staged_path = PathBuf::from(staged); + if !staged_path.exists() { + return; + } + let Ok(dest) = current_app_bundle() else { return }; + if swap_app(&staged_path, &dest).is_ok() { + let _ = save_manifest( + app, + &Staging { + version: m.version, + staged_app: None, + ready: false, + applied: true, + }, + ); + if let Ok(dir) = updates_dir(app) { + let _ = std::fs::remove_dir_all(dir.join("staged")); + } + } +} + +/// Startup housekeeping: if a staged update was applied (we're now running a +/// version >= the staged one), clean it up and toast. Call before the first +/// check. +pub fn init_on_startup(app: &AppHandle) { + let Some(m) = load_manifest(app) else { return }; + // Running a version at or beyond the staged one → the staging is obsolete + // (applied on the previous quit, or superseded by a manual install). + if !is_newer(&m.version, CURRENT_VERSION) { + if let Ok(dir) = updates_dir(app) { + let _ = std::fs::remove_dir_all(&dir); + } + if m.applied { + let _ = app.emit("atlas:update-applied", serde_json::json!({ "version": CURRENT_VERSION })); + } + } +} + +/// Periodically re-check for updates while the app runs. +pub fn spawn_periodic(app: &AppHandle) { + let app = app.clone(); + tauri::async_runtime::spawn(async move { + let mut interval = tokio::time::interval(RECHECK_INTERVAL); + interval.tick().await; // consume the immediate first tick (startup already checked) + loop { + interval.tick().await; + check_in_background(&app); + } + }); +} + +/// Swap `staged` over `dest` with a `.bak` rollback. Tries an atomic rename +/// (same APFS volume — instant); falls back to a `ditto` copy. +fn swap_app(staged: &Path, dest: &Path) -> Result<(), String> { + let backup = dest.with_extension("app.bak"); + let _ = std::fs::remove_dir_all(&backup); + if dest.exists() { + std::fs::rename(dest, &backup).map_err(|e| format!("back up current app: {e}"))?; + } + // Fast path: atomic directory rename on the same volume. + if std::fs::rename(staged, dest).is_err() { + let out = Command::new("ditto") + .arg(staged) + .arg(dest) + .output() + .map_err(|e| format!("ditto: {e}"))?; + if !out.status.success() { + // Roll back to the original bundle. + let _ = std::fs::remove_dir_all(dest); + if backup.exists() { + let _ = std::fs::rename(&backup, dest); + } + return Err(format!( + "install failed: {}", + String::from_utf8_lossy(&out.stderr).trim() + )); + } + } + let _ = std::fs::remove_dir_all(&backup); + Ok(()) +} + +/// `codesign --verify --deep --strict` + Team ID match + `spctl` Gatekeeper +/// assessment. All three must pass. +fn verify_signature(app_path: &Path) -> Result<(), String> { + let verify = Command::new("codesign") + .args(["--verify", "--deep", "--strict", "--verbose=2"]) + .arg(app_path) + .output() + .map_err(|e| format!("codesign: {e}"))?; + if !verify.status.success() { + return Err("update rejected: code signature invalid".into()); + } + + let info = Command::new("codesign") + .args(["-dvvv"]) + .arg(app_path) + .output() + .map_err(|e| format!("codesign -dvvv: {e}"))?; + let meta = String::from_utf8_lossy(&info.stderr); + let team_ok = meta + .lines() + .any(|l| l.trim() == format!("TeamIdentifier={EXPECTED_TEAM_ID}")); + if !team_ok { + return Err("update rejected: unexpected signing team".into()); + } + + let spctl = Command::new("spctl") + .args(["--assess", "--type", "execute", "--verbose=2"]) + .arg(app_path) + .output() + .map_err(|e| format!("spctl: {e}"))?; + if !spctl.status.success() { + return Err("update rejected: notarization check failed".into()); + } + Ok(()) +} + +/// Resolve the running `Atlas.app` bundle root from the executable path +/// (`…/Atlas.app/Contents/MacOS/atlas` → `…/Atlas.app`). +fn current_app_bundle() -> Result { + let exe = std::env::current_exe().map_err(|e| format!("current_exe: {e}"))?; + let app = exe + .parent() // MacOS + .and_then(|p| p.parent()) // Contents + .and_then(|p| p.parent()) // Atlas.app + .map(|p| p.to_path_buf()) + .ok_or_else(|| "could not resolve app bundle path".to_string())?; + if app.extension().map(|x| x == "app").unwrap_or(false) { + Ok(app) + } else { + Err(format!( + "running from a non-.app location ({}); update via the DMG", + app.display() + )) + } +} diff --git a/src-tauri/src/commands/updater_stub.rs b/src-tauri/src/commands/updater_stub.rs new file mode 100644 index 00000000..4f1802f4 --- /dev/null +++ b/src-tauri/src/commands/updater_stub.rs @@ -0,0 +1,73 @@ +//! No-op auto-updater stand-in for non-macOS targets. +//! +//! The real updater ([`crate::commands::updater`], gated to +//! `#[cfg(target_os = "macos")]`) mounts/verifies/swaps a signed `.app` +//! bundle — none of that has a Windows/Linux equivalent yet. This stub keeps +//! `lib.rs` and the frontend's `invoke()` surface platform-agnostic: every +//! command still exists and returns a well-formed "nothing to do" response +//! instead of failing to compile or erroring at call time. + +use serde::Serialize; +use tauri::AppHandle; + +/// The running app's version (compile-time), mirrored from the real impl so +/// `UpdaterSnapshot`/`UpdateStatus` payloads stay shaped the same. +const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Default)] +pub struct UpdaterState; + +impl UpdaterState { + pub fn new() -> Self { + Self + } +} + +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdateStatus { + pub available: bool, + pub version: Option, + pub current_version: String, +} + +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdaterSnapshot { + pub phase: String, + pub version: Option, + pub current_version: String, +} + +pub fn init_on_startup(_app: &AppHandle) {} +pub fn check_in_background(_app: &AppHandle) {} +pub fn spawn_periodic(_app: &AppHandle) {} +pub fn apply_on_exit(_app: &AppHandle) {} + +#[tauri::command] +pub async fn update_check_now() -> Result { + Ok(UpdateStatus { + available: false, + version: None, + current_version: CURRENT_VERSION.to_string(), + }) +} + +#[tauri::command] +pub fn update_state() -> UpdaterSnapshot { + UpdaterSnapshot { + phase: "idle".into(), + version: None, + current_version: CURRENT_VERSION.to_string(), + } +} + +#[tauri::command] +pub fn update_ignore(_version: String) -> Result<(), String> { + Ok(()) +} + +#[tauri::command] +pub async fn update_apply() -> Result<(), String> { + Err("auto-update isn't available on this platform yet".into()) +}