diff --git a/.github/logo.png b/.github/logo.png
new file mode 100644
index 000000000000..085d001dad8c
Binary files /dev/null and b/.github/logo.png differ
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a0c158c45d8e..000000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,15 +0,0 @@
-[submodule "apps/ios"]
- path = apps/ios
- url = https://github.com/spacedriveapp/ios.git
-[submodule "apps/macos"]
- path = apps/macos
- url = https://github.com/spacedriveapp/macos.git
-[submodule "workbench"]
- path = docs/workbench
- url = https://github.com/spacedriveapp/design.git
-[submodule "apps/landing"]
- path = apps/landing
- url = https://github.com/spacedriveapp/landing.git
-[submodule "apps/api"]
- path = apps/api
- url = https://github.com/spacedriveapp/api.git
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 473990a51139..589f5d560866 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -576,6 +576,58 @@ The Tauri app consists of:
The app connects to `sd-daemon` which manages libraries and P2P connections. In dev mode, the daemon is started automatically by the `dev:with-daemon` script.
+## SpaceUI (Design System)
+
+Spacedrive's UI components come from [SpaceUI](https://github.com/spacedriveapp/spaceui), a standalone design system monorepo. SpaceUI provides shared primitives (Button, Input, Dialog, etc.), design tokens, form components, AI agent UI, and explorer components used across Spacedrive and Spacebot.
+
+### Packages
+
+| Package | Description |
+|---------|-------------|
+| `@spacedrive/tokens` | Design tokens, semantic color system, Tailwind v4 theme |
+| `@spacedrive/primitives` | Base UI components built on Radix UI |
+| `@spacedrive/forms` | Form field wrappers for react-hook-form |
+| `@spacedrive/ai` | AI agent interaction components |
+| `@spacedrive/explorer` | File management components |
+
+### Working on UI Alongside Spacedrive
+
+If you're contributing to both the UI layer and the app, clone SpaceUI as a sibling directory:
+
+```bash
+# From your workspace root (e.g., ~/Projects)
+git clone https://github.com/spacedriveapp/spacedrive
+git clone https://github.com/spacedriveapp/spaceui
+
+# Your directory should look like:
+# ~/Projects/
+# ├── spacedrive/
+# └── spaceui/
+```
+
+Then link SpaceUI for local development:
+
+```bash
+# Register SpaceUI packages globally
+cd spaceui
+bun install
+bun run link
+
+# Link into Spacedrive
+cd ../spacedrive
+bun link @spacedrive/tokens @spacedrive/primitives @spacedrive/ai
+```
+
+With linking active, changes you make in `spaceui/` are picked up immediately by Spacedrive's Vite dev server — no rebuild needed. The Vite configs in `apps/tauri/` and `apps/web/` already have the necessary source aliases, `optimizeDeps.exclude`, and `server.fs.allow` settings configured.
+
+### If You're Only Working on Spacedrive
+
+If you're not modifying SpaceUI itself, you don't need to clone it. Spacedrive consumes published `@spacedrive/*` packages from npm. Just run `bun install` and everything resolves from the registry.
+
+### SpaceUI Integration Guide
+
+For full details on how SpaceUI is integrated (Vite aliases, Tailwind `@source` scanning, React deduplication, publishing workflow), see the [SpaceUI Integration Guide](https://github.com/spacedriveapp/spaceui/blob/main/INTEGRATION.md).
+
## Extension Development
Spacedrive supports WASM-based extensions for adding custom functionality. Extensions run in sandboxed environments with full access to the Spacedrive SDK.
diff --git a/Cargo.lock b/Cargo.lock
index 7fa3880e3ffe..f30d151ea37a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1010,9 +1010,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "bytemuck"
-version = "1.23.2"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
+checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
[[package]]
name = "byteorder"
@@ -2754,7 +2754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
dependencies = [
"bit_field",
- "half 2.6.0",
+ "half 2.4.1",
"lebe",
"miniz_oxide",
"rayon-core",
@@ -3628,6 +3628,24 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+[[package]]
+name = "global-hotkey"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
+dependencies = [
+ "crossbeam-channel",
+ "keyboard-types",
+ "objc2 0.6.3",
+ "objc2-app-kit",
+ "once_cell",
+ "serde",
+ "thiserror 2.0.16",
+ "windows-sys 0.59.0",
+ "x11rb",
+ "xkeysym",
+]
+
[[package]]
name = "globset"
version = "0.4.16"
@@ -3763,9 +3781,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
[[package]]
name = "half"
-version = "2.6.0"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
@@ -8513,6 +8531,40 @@ dependencies = [
"smallvec",
]
+[[package]]
+name = "rust-embed"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.106",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
+dependencies = [
+ "sha2 0.10.9",
+ "walkdir",
+]
+
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -8845,6 +8897,26 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "sd-archive"
+version = "0.1.0"
+dependencies = [
+ "async-trait",
+ "blake3",
+ "chrono",
+ "dashmap",
+ "futures",
+ "indexmap 2.11.4",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "thiserror 2.0.16",
+ "tokio",
+ "toml 0.8.23",
+ "tracing",
+ "uuid",
+]
+
[[package]]
name = "sd-bench"
version = "0.1.0"
@@ -8975,6 +9047,7 @@ dependencies = [
"rmp",
"rmp-serde",
"rubato",
+ "sd-archive",
"sd-ffmpeg",
"sd-fs-watcher",
"sd-images",
@@ -9151,7 +9224,10 @@ dependencies = [
"axum",
"axum-extra",
"clap",
+ "futures",
"http 1.3.1",
+ "mime_guess",
+ "rust-embed",
"sd-core",
"secstr",
"serde",
@@ -9159,6 +9235,7 @@ dependencies = [
"tempfile",
"thiserror 1.0.69",
"tokio",
+ "tokio-stream",
"tower 0.4.13",
"tower-http 0.5.2",
"tracing",
@@ -10046,6 +10123,7 @@ dependencies = [
"tauri-plugin-clipboard-manager",
"tauri-plugin-dialog",
"tauri-plugin-fs",
+ "tauri-plugin-global-shortcut",
"tauri-plugin-os",
"tauri-plugin-shell",
"tauri-plugin-updater",
@@ -10986,6 +11064,21 @@ dependencies = [
"url",
]
+[[package]]
+name = "tauri-plugin-global-shortcut"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
+dependencies = [
+ "global-hotkey",
+ "log 0.4.28",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.16",
+]
+
[[package]]
name = "tauri-plugin-os"
version = "2.3.2"
@@ -11238,7 +11331,7 @@ checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
dependencies = [
"fax",
"flate2",
- "half 2.6.0",
+ "half 2.4.1",
"quick-error",
"weezl",
"zune-jpeg",
@@ -13776,6 +13869,12 @@ dependencies = [
"rustix 1.1.2",
]
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+
[[package]]
name = "xml-rs"
version = "0.8.27"
diff --git a/Cargo.toml b/Cargo.toml
index 47a88fcdd2ce..e9cb6c50b9f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ resolver = "2"
[workspace.package]
edition = "2021"
-license = "AGPL-3.0-only"
+license = "FSL-1.1-ALv2"
repository = "https://github.com/spacedriveapp/spacedrive"
rust-version = "1.81"
@@ -90,6 +90,15 @@ uhlc = "0.8.0" # Must fo
uuid = "1.10" # Must follow version used by specta
webp = "0.3.0"
zeroize = "1.8"
+sqlx = { version = "0.8", default-features = false, features = ["sqlite", "runtime-tokio"] }
+lancedb = "0.15"
+fastembed = "4"
+arrow-array = "53.3"
+arrow-schema = "53.3"
+indexmap = "2.7"
+dashmap = "6.1"
+toml = "0.8"
+ort = { version = "2.0.0-rc.9", default-features = false, features = ["download-binaries", "tls-native"] }
# Proper IOS Support
# TODO: Fix the fork - it has compilation errors with TimerFuture
diff --git a/LICENSE b/LICENSE
index b3ce5ea17282..5dba2778c7ff 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,663 +1,128 @@
-Copyright (c) 2021-present Spacedrive Technology Inc.
-
- GNU AFFERO GENERAL PUBLIC LICENSE
- Version 3, 19 November 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU Affero General Public License is a free, copyleft license for
-software and other kinds of works, specifically designed to ensure
-cooperation with the community in the case of network server software.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-our General Public Licenses are intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- Developers that use our General Public Licenses protect your rights
-with two steps: (1) assert copyright on the software, and (2) offer
-you this License which gives you legal permission to copy, distribute
-and/or modify the software.
-
- A secondary benefit of defending all users' freedom is that
-improvements made in alternate versions of the program, if they
-receive widespread use, become available for other developers to
-incorporate. Many developers of free software are heartened and
-encouraged by the resulting cooperation. However, in the case of
-software used on network servers, this result may fail to come about.
-The GNU General Public License permits making a modified version and
-letting the public access it on a server without ever releasing its
-source code to the public.
-
- The GNU Affero General Public License is designed specifically to
-ensure that, in such cases, the modified source code becomes available
-to the community. It requires the operator of a network server to
-provide the source code of the modified version running there to the
-users of that server. Therefore, public use of a modified version, on
-a publicly accessible server, gives the public access to the source
-code of the modified version.
-
- An older license, called the Affero General Public License and
-published by Affero, was designed to accomplish similar goals. This is
-a different license, not a version of the Affero GPL, but Affero has
-released a new version of the Affero GPL which permits relicensing under
-this license.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU Affero General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Remote Network Interaction; Use with the GNU General Public License.
-
- Notwithstanding any other provision of this License, if you modify the
-Program, your modified version must prominently offer all users
-interacting with it remotely through a computer network (if your version
-supports such interaction) an opportunity to receive the Corresponding
-Source of your version by providing access to the Corresponding Source
-from a network server at no charge, through some standard or customary
-means of facilitating copying of software. This Corresponding Source
-shall include the Corresponding Source for any work covered by version 3
-of the GNU General Public License that is incorporated pursuant to the
-following paragraph.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the work with which it is combined will remain governed by version
-3 of the GNU General Public License.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU Affero General Public License from time to time. Such new versions
-will be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU Affero General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU Affero General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU Affero General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source. For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code. There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
-.
\ No newline at end of file
+# Functional Source License, Version 1.1, ALv2 Future License
+
+## Abbreviation
+
+FSL-1.1-ALv2
+
+## Notice
+
+Copyright 2026 Spacedrive Technology Inc.
+
+## Terms and Conditions
+
+### Licensor ("We")
+
+The party offering the Software under these Terms and Conditions.
+
+### The Software
+
+The "Software" is each version of the software that we make available under
+these Terms and Conditions, as indicated by our inclusion of these Terms and
+Conditions with the Software.
+
+### License Grant
+
+Subject to your compliance with this License Grant and the Patents,
+Redistribution and Trademark clauses below, we hereby grant you the right to
+use, copy, modify, create derivative works, publicly perform, publicly display
+and redistribute the Software for any Permitted Purpose identified below.
+
+### Permitted Purpose
+
+A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
+means making the Software available to others in a commercial product or
+service that:
+
+1. substitutes for the Software;
+
+2. substitutes for any other product or service we offer using the Software
+ that exists as of the date we make the Software available; or
+
+3. offers the same or substantially similar functionality as the Software.
+
+### Additional Use Restrictions
+
+Notwithstanding the general definition of Competing Use above, you may not use
+the Software to:
+
+1. Provide the Software as a managed cloud service, hosted service, or
+ software-as-a-service offering to third parties;
+
+2. Offer commercial hosting, deployment, or management of Spacedrive instances
+ as a service to third parties;
+
+3. Provide cloud storage, file synchronization, or data management services to
+ third parties based on or incorporating the Software; or
+
+4. Offer any managed AI agent services or automation platforms to third parties
+ that are based on or incorporate the Software.
+
+These restrictions apply regardless of whether Spacedrive Technology Inc.
+currently offers such services, and are intended to preserve our ability to
+offer these categories of services in the future.
+
+### Permitted Purposes
+
+Permitted Purposes specifically include using the Software:
+
+1. for your internal use and access;
+
+2. for non-commercial education;
+
+3. for non-commercial research; and
+
+4. in connection with professional services that you provide to a licensee
+ using the Software in accordance with these Terms and Conditions.
+
+### Patents
+
+To the extent your use for a Permitted Purpose would necessarily infringe our
+patents, the license grant above includes a license under our patents. If you
+make a claim against any party that the Software infringes or contributes to
+the infringement of any patent, then your patent license to the Software ends
+immediately.
+
+### Redistribution
+
+The Terms and Conditions apply to all copies, modifications and derivatives of
+the Software.
+
+If you redistribute any copies, modifications or derivatives of the Software,
+you must include a copy of or a link to these Terms and Conditions and not
+remove any copyright notices provided in or with the Software.
+
+### Disclaimer
+
+THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
+PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
+
+IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
+SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
+EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
+
+### Trademarks
+
+Except for displaying the License Details and identifying us as the origin of
+the Software, you have no right under these Terms and Conditions to use our
+trademarks, trade names, service marks or product names.
+
+## Grant of Future License
+
+We hereby irrevocably grant you an additional license to use the Software under
+the Apache License, Version 2.0 that is effective on the second anniversary of
+the date we make the Software available. On or after that date, you may use the
+Software under the Apache License, Version 2.0, in which case the following
+will apply:
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
diff --git a/README.md b/README.md
index 11c854e453f8..2e65e462708f 100644
--- a/README.md
+++ b/README.md
@@ -1,408 +1,188 @@
-Spacedrive is an open source cross-platform file manager, powered by a virtual distributed filesystem (VDFS) written in Rust.
+
Spacedrive
-Organize files across multiple devices, clouds, and platforms from a single interface. Tag once, access everywhere. Never lose track of where your files are.
+
+ One file manager for all your devices and clouds.
+ Powered by a Virtual Distributed File System, complete with apps for macOS, Windows, Linux, iOS and Android
+
-> [!IMPORTANT]
-> **v2.0.0-alpha.1 Released: December 26, 2025**
->
-> This is Spacedrive v2—a complete ground-up rewrite. After development of the original alpha version stopped in January this year, I rebuilt Spacedrive from scratch with the hard lessons learned.
->
-> **Current status:** Alpha release for macOS and Linux. Windows support coming in alpha.2. Mobile apps (iOS/Android) coming soon.
->
-> **[Download Release](https://github.com/spacedriveapp/spacedrive/releases/tag/v2.0.0-alpha.1)** · Visit [v2.spacedrive.com](https://v2.spacedrive.com) for complete documentation and guides.
->
-> If you're looking for the previous version, see the [v1 branch](https://github.com/spacedriveapp/spacedrive/tree/v1).
+
-Computing was designed for a single-device world. The file managers we use today—Finder, Explorer, Files—were built when your data lived in one place: the computer in front of you.
+---
-The shift to multi-device computing forced us into cloud ecosystems. Want your files everywhere? Upload them to someone else's servers. The convenience came at a cost: **data ownership**. This wasn't accidental—centralization was the path of least resistance for solving multi-device sync.
+## What is Spacedrive?
-Now AI is accelerating this trend. Cloud services offer intelligent file analysis and semantic search, but only if you upload your data to their infrastructure. As we generate more data and AI becomes more capable, we're giving away more and more to access basic computing conveniences.
+Spacedrive is a cross-device data platform. Index files, emails, notes, and external sources. Search everything. Sync via P2P. Keep AI agents safe with built-in screening.
-**The current system isn't built for a world where:**
+- **Content identity** — every file gets a BLAKE3 content hash. Same file on two devices produces the same hash. Spacedrive tracks redundancy and deduplication across all your machines.
+- **Cross-device** — see all your files across all your devices in one place. Files on disconnected devices stay in the index and appear as offline.
+- **P2P sync** — devices connect directly via Iroh/QUIC. No servers, no cloud, no single point of failure. Metadata syncs between devices. Files stay where they are.
+- **Cloud volumes** — index S3, Google Drive, Dropbox, OneDrive, Azure, and GCS as first-class volumes alongside local storage.
+- **Nine views** — grid, list, columns, media, size, recents, search, knowledge, and splat. QuickPreview for video, audio, code, documents, 3D, and images.
+- **Local-first** — everything runs on your machine. No data leaves your device unless you choose to sync between your own devices.
-- You own multiple devices with underutilized compute and storage
-- Local AI models are becoming competitive with cloud alternatives
-- Privacy and data sovereignty matter
-- You shouldn't have to choose between convenience and control
+### Is this a replacement for Finder or Explorer?
-## The Vision
+No. Spacedrive sits above your OS file manager and adds capabilities Finder/Explorer lack:
-Spacedrive is infrastructure for the next era of computing. It's an architecture designed for multi-device environments from the ground up—not cloud services retrofitted with offline support, but local-first sync that scales to the cloud when you want it.
+- **Portal across everything** — search and browse files across local disks, external drives, NAS, cloud storage, and archived data sources from one interface.
+- **Operating surface for files** — content identity, sidecars, derivative artifacts, rich metadata, sync, and cross-device awareness built into the core model.
+- **Embeddable and shareable** — run it as a desktop app, headless server, hosted file service, or embed the interface and APIs into other products.
+- **AI-ready by design** — indexing and analysis pipelines prepare data ahead of time instead of giving agents raw shell access.
+- **Safer access model** — route AI and automation through structured APIs, permissions, and processing layers instead of direct file operations.
-As local AI models improve, Spacedrive becomes the fabric that enables the same insights cloud services offer today, but running on hardware you already own, on data that never leaves your control. This is a long-term project correcting computing's trajectory toward centralization.
+You still use your OS for low-level file interactions. Spacedrive adds the cross-platform, cross-device, cloud-aware, and automation-friendly layer on top.
-The file explorer interface is deliberate. Everyone understands it. It's seen the least innovation in decades. And it has the most potential when you bake distributed computing, content awareness, and local AI into something universally familiar.
+### Data Archival
-## How It Works
+Spacedrive indexes external data sources via script-based adapters: Gmail, Apple Notes, Chrome bookmarks, Obsidian, Slack, GitHub, calendar events, contacts. Each source becomes a searchable repository alongside your files.
-Spacedrive treats files as **first-class objects with content identity**, not paths. A photo on your laptop and the same photo on your NAS are recognized as one piece of content. This enables:
+Adapters are a folder with an `adapter.toml` manifest and a sync script in any language. If it reads stdin and prints lines, it works.
-- **Content-aware deduplication** - Track redundancy across all devices
-- **Semantic search** - Find files in under 100ms across millions of entries
-- **Transactional operations** - Preview conflicts, space savings, and outcomes before execution
-- **Peer-to-peer sync** - No servers, no consensus protocols, no single point of failure
-- **Offline-first** - Full functionality without internet, syncs when devices reconnect
+**Shipped adapters:** Gmail, Apple Notes, Chrome Bookmarks, Chrome History, Safari History, Obsidian, OpenCode, Slack, macOS Contacts, macOS Calendar, GitHub.
-Files stay where they are. Spacedrive just makes them universally addressable with rich metadata and cross-device intelligence.
+### Spacebot
----
+Spacedrive integrates with [Spacebot](https://github.com/spacedriveapp/spacebot), an open source AI agent runtime. Spacebot runs as an optional separate process. Spacedrive provides the data, permission, and execution layer. Spacebot provides the intelligence.
-## Architecture
+Each Spacebot instance pairs with one Spacedrive node as its home device. That node authenticates the agent, maintains the device graph, resolves permissions, and forwards operations to peer devices. Every device in your library can reach Spacebot through the paired node over P2P (Iroh/QUIC) without direct network access. One agent runtime serves your entire device fleet.
-Spacedrive is built on four core principles:
+When Spacebot spawns a worker, that worker can target any device in the library. File reads, shell commands, and operations proxy through Spacedrive to the target device. Talk to the agent from your phone while work executes on a server. Read files from a NAS, run commands on a workstation, report to a laptop — all in one task.
-### 1. Virtual Distributed Filesystem (VDFS)
+Every operation passes through Spacedrive's permission system: which devices the agent can access, which paths are readable or writable, which operations are allowed, and which require human confirmation. The paired node resolves effective policy before forwarding. One security model, one audit surface across all devices and clouds.
-Files and folders become first-class objects with rich metadata, independent of their physical location. Every file gets a universal address (`SdPath`) that works across devices. Content-aware addressing means you can reference files by what they contain, not just where they live.
+### File System Intelligence
-### 2. Content Identity System
+Spacedrive adds intelligence to your filesystem by combining three layers:
-Adaptive hashing (BLAKE3 with strategic sampling for large files) creates a unique fingerprint for every piece of content. This enables:
+- **File intelligence** — derivative data like OCR, transcripts, extracted metadata, thumbnails, previews, classifications, and sidecars.
+- **Directory intelligence** — contextual knowledge attached to folders and subtrees ("active projects", "dormant archives", etc).
+- **Access intelligence** — permissions and policy that apply across devices and clouds, routing agents through structured access instead of raw shell commands.
-- **Deduplication**: Recognize identical files across devices
-- **Redundancy tracking**: Know where your backups are
-- **Content-based operations**: "Copy this file from wherever it's available"
+When an agent navigates through Spacedrive, it receives the file listing, subtree context, effective permissions, and summaries. Users can explain how they organize their system. Agents can add attributed notes. Jobs generate summaries from structure and activity. The intelligence stays attached to the filesystem, not buried in temporary session memory.
-### 3. Transactional Actions
+### Safety Screening
-Every file operation can be previewed before execution. See exactly what will happen—space savings, conflicts, estimated time—then approve or cancel. Operations become durable jobs that survive network interruptions and device restarts.
+When enabled, every record passes through a safety pipeline before becoming searchable:
-### 4. Leaderless Sync
+- **Prompt Guard 2** — local classifier detects prompt injection in emails, messages, and documents before they enter the index.
+- **Trust tiers** — authored content (your notes) gets balanced screening, external content (email inbox) gets strict screening.
+- **Quarantine system** — flagged records excluded from AI agent queries, reviewable in desktop app.
+- **Content fencing** — search results include trust metadata so agents know what's safe vs untrusted.
-Peer-to-peer synchronization without central coordinators. Device-specific data (your filesystem index) uses state replication. Shared metadata (tags, ratings) uses a lightweight HLC-ordered log with deterministic conflict resolution. No leader election, no single point of failure.
+No other local data tool screens indexed content before exposing it to AI.
---
-## Core Features
-
-| Feature | Description |
-| ----------------------- | ---------------------------------------------------------------------------- |
-| **Cross-Platform** | macOS, Windows, Linux, iOS, Android |
-| **Multi-Device Index** | Unified view of files across all your devices |
-| **Content Addressing** | Find optimal file copies automatically (local-first, then LAN, then cloud) |
-| **Smart Deduplication** | Identify identical files regardless of name or location |
-| **Cloud Integration** | Index S3, Google Drive, Dropbox as first-class volumes |
-| **P2P Networking** | Direct device connections with automatic NAT traversal (Iroh + QUIC) |
-| **Semantic Tags** | Graph-based tagging with hierarchies, aliases, and contextual disambiguation |
-| **Action Preview** | Simulate any operation before execution |
-| **Offline-First** | Full functionality without internet, syncs when devices reconnect |
-| **Local Backup** | P2P backup between your own devices (iOS photo backup available now) |
-| **Extension System** | WASM-based plugins for domain-specific functionality |
-
----
-
-## Tech Stack
-
-**Core**
-
-- **Rust** - Entire VDFS implementation (~183k lines)
-- **Tokio** - Async runtime
-- **SQLite + SeaORM** - Local-first database with type-safe ORM queries
-- **Iroh** - P2P networking with QUIC transport, hole-punching, and local discovery
-- **BLAKE3** - Fast cryptographic hashing for content identity
-- **Wasmer** - Sandboxed WASM extension runtime
-- **Axum** - HTTP/GraphQL server for web and API access
-- **OpenDAL** - Unified cloud storage abstraction (S3, Google Drive, OneDrive, Dropbox, Azure Blob, GCS)
-- **Specta** - Auto-generated TypeScript and Swift types from Rust
-
-**Cryptography & Security**
-
-- **Ed25519 / X25519** - Signatures and key exchange
-- **ChaCha20-Poly1305 / AES-GCM** - Authenticated encryption
-- **Argon2** - Password hashing
-- **BIP39** - Mnemonic phrase support for key backup
-- **redb** - Encrypted key-value store for credentials
-
-**Media Processing**
-
-- **FFmpeg** (via custom `sd-ffmpeg` crate) - Video thumbnails, audio extraction
-- **libheif** - HEIF/HEIC image support
-- **Pdfium** - PDF rendering
-- **Whisper** - On-device speech recognition (Metal-accelerated on Apple platforms)
-- **Blurhash** - Compact image placeholders
-
-**Interface** (shared across web and desktop)
-
-- **React 19** - UI framework
-- **Vite** - Build tooling
-- **TypeScript** - Type-safe frontend code
-- **TanStack Query** - Server state management
-- **Zustand** - Client state management
-- **Radix UI** - Accessible headless components
-- **Tailwind CSS** - Utility-first styling
-- **Framer Motion** - Animations
-- **React Hook Form + Zod** - Form management and validation
-- **Three.js / React Three Fiber** - 3D visualization
-- **dnd-kit** - Drag and drop
-- **TanStack Virtual / TanStack Table** - Virtualized lists and tables
+## Architecture
-**Desktop**
+The core is built on four principles:
-- **Tauri 2** - Cross-platform desktop shell (macOS, Linux, Windows)
+1. **Virtual Distributed Filesystem (VDFS)** — files and folders become first-class objects with rich metadata, independent of physical location. Every file gets a universal address (`SdPath`) that works across devices.
-**Mobile (React Native)**
+2. **Content Identity System** — adaptive hashing (BLAKE3 with strategic sampling for large files) creates a unique fingerprint for every piece of content. Enables deduplication, redundancy tracking, and content-based operations.
-- **React Native** 0.81 + **Expo** - Cross-platform mobile framework
-- **Expo Router** - File-based routing
-- **NativeWind** - Tailwind CSS for React Native
-- **React Navigation** - Native navigation stack
-- **Reanimated** - Native-thread animations
-- **sd-mobile-core** - Rust core bridge via FFI
+3. **Transactional Actions** — every file operation can be previewed before execution. See space savings, conflicts, and estimated time, then approve or cancel. Operations become durable jobs that survive network interruptions and device restarts.
-**Architecture Patterns**
+4. **Leaderless Sync** — peer-to-peer synchronization without central coordinators. Device-specific data uses state replication. Shared metadata uses an HLC-ordered log with deterministic conflict resolution.
-- Event-driven design with centralized EventBus
-- CQRS: Actions (mutations) and Queries (reads) with preview-commit-verify
-- Durable jobs with MessagePack serialization and checkpointing
-- Domain-separated sync with clear data ownership boundaries
-- Compile-time operation registration via `inventory` crate
+The implementation is a single Rust crate with CQRS/DDD architecture. Every operation (file copy, tag create, search query) is a registered action or query with type-safe input/output that auto-generates TypeScript types for the frontend.
----
-
-## Project Structure
+| Component | Technology |
+| --------------- | -------------------------------------------- |
+| Language | Rust |
+| Async runtime | Tokio |
+| Database | SQLite (SeaORM + sqlx) |
+| P2P | Iroh (QUIC, hole-punching, local discovery) |
+| Content hashing | BLAKE3 |
+| Vector search | LanceDB + FastEmbed |
+| Cloud storage | OpenDAL |
+| Cryptography | Ed25519, X25519, ChaCha20-Poly1305, AES-GCM |
+| Media | FFmpeg, libheif, Pdfium, Whisper |
+| Desktop | Tauri 2 |
+| Mobile | React Native + Expo |
+| Frontend | React 19, Vite, TanStack Query, Tailwind CSS v4 |
+| Design system | [SpaceUI](https://github.com/spacedriveapp/spaceui) (shared component library) |
+| Type generation | Specta |
```
spacedrive/
-├── core/ # Rust VDFS implementation
-│ ├── src/
-│ │ ├── domain/ # Core models (Entry, Library, Device, Tag, Volume)
-│ │ ├── ops/ # CQRS operations (actions & queries)
-│ │ ├── infra/ # Infrastructure (DB, events, jobs, sync)
-│ │ ├── service/ # High-level services (network, file sharing, sync)
-│ │ ├── crypto/ # Key management and encryption
-│ │ ├── device/ # Device identity and configuration
-│ │ ├── filetype/ # File type detection and registry
-│ │ ├── location/ # Location management and indexing
-│ │ ├── library/ # Library lifecycle and operations
-│ │ └── volume/ # Volume detection and fingerprinting
-│ └── tests/ # Integration tests (pairing, sync, file transfer)
+├── core/ # Rust engine (CQRS/DDD)
├── apps/
-│ ├── cli/ # CLI and daemon entry point
-│ ├── server/ # Headless server for Docker/self-hosting
-│ ├── tauri/ # Desktop app shell (macOS, Windows, Linux)
-│ ├── web/ # Web app (Vite, connects to daemon via WebSocket)
-│ ├── mobile/ # React Native mobile app (Expo)
-│ ├── api/ # Cloud API server (Bun + Elysia)
-│ ├── landing/ # Marketing site and docs (Next.js)
-│ ├── ios/ # Native iOS prototype (Swift)
-│ ├── macos/ # Native macOS prototype (Swift)
-│ └── gpui-photo-grid/ # GPUI media viewer prototype
+│ ├── tauri/ # Desktop app (macOS, Windows, Linux)
+│ ├── mobile/ # React Native (iOS, Android)
+│ ├── cli/ # CLI and daemon
+│ ├── server/ # Headless server
+│ └── web/ # Browser client
├── packages/
-│ ├── interface/ # Shared React UI (used by web and desktop)
-│ ├── ts-client/ # Auto-generated TypeScript client and hooks
-│ ├── swift-client/ # Auto-generated Swift client
-│ ├── ui/ # Shared component library
-│ └── assets/ # Icons and images
-├── crates/
-│ ├── crypto/ # Cryptographic primitives
-│ ├── ffmpeg/ # FFmpeg bindings for video/audio
-│ ├── images/ # Image processing (HEIF, PDF, SVG)
-│ ├── media-metadata/ # EXIF/media metadata extraction
-│ ├── fs-watcher/ # Cross-platform file system watcher
-│ ├── sdk/ # WASM extension SDK
-│ ├── sdk-macros/ # Extension procedural macros
-│ ├── task-system/ # Durable job execution engine
-│ ├── sd-client/ # Rust client library
-│ └── ... # actors, fda, log-analyzer, utils
-├── extensions/ # WASM extensions (photos, test-extension)
-└── docs/ # Architecture documentation
+│ ├── interface/ # Shared React UI
+│ ├── ts-client/ # Auto-generated TypeScript client
+│ ├── ui/ # Component library
+│ └── assets/ # Icons, images, SVGs
+├── crates/ # Standalone Rust crates (ffmpeg, crypto, etc.)
+├── adapters/ # Script-based data source adapters
+└── schemas/ # TOML data type schemas
```
---
-## Extensions
-
-Spacedrive's WASM-based extension system enables specialized functionality while maintaining security and portability.
-
-> [!NOTE]
-> The extension system is under active development. A stable SDK API will be available in a future release.
-
-### Professional Extensions
-
-| Extension | Purpose | Key Features | Status |
-| ------------- | ------------------------------- | --------------------------------------------------------------------------- | ----------- |
-| **Photos** | AI-powered photo management | Face recognition, place identification, moments, scene classification | In Progress |
-| **Chronicle** | Research & knowledge management | Document analysis, knowledge graphs, AI summaries | In Progress |
-| **Atlas** | Dynamic CRM & team knowledge | Runtime schemas, contact tracking, deal pipelines | In Progress |
-| **Studio** | Digital asset management | Scene detection, transcription, proxy generation | Planned |
-| **Ledger** | Financial intelligence | Receipt OCR, expense tracking, tax preparation | Planned |
-| **Guardian** | Backup & redundancy monitoring | Content identity tracking, zero-redundancy alerts, smart backup suggestions | Planned |
-| **Cipher** | Security & encryption | Password manager, file encryption, breach alerts | Planned |
-
-### Open Source Archive Extensions
-
-| Extension | Purpose | Provides Data For | Status |
-| ------------------- | ----------------------- | ------------------------ | ------- |
-| **Email Archive** | Gmail/Outlook backup | Atlas, Ledger, Chronicle | Planned |
-| **Chrome History** | Browsing history backup | Chronicle | Planned |
-| **Spotify Archive** | Listening history | Analytics | Planned |
-| **GPS Tracker** | Location timeline | Photos, Analytics | Planned |
-| **Tweet Archive** | Twitter backup | Chronicle, Analytics | Planned |
-| **GitHub Tracker** | Repository tracking | Chronicle | Planned |
-
----
-
## Getting Started
-### Prerequisites
-
-- **Rust** 1.81+ ([rustup](https://rustup.rs/))
-- **Bun** 1.3+ ([bun.sh](https://bun.sh)) - For Tauri desktop app
-
-### Quick Start with Desktop App (Tauri)
-
-Spacedrive runs as a daemon (`sd-daemon`) that manages your libraries and P2P connections. The Tauri desktop app can launch its own daemon instance, or connect to a daemon started by the CLI.
+Requires [Rust](https://rustup.rs/) 1.81+, [Bun](https://bun.sh) 1.3+, [just](https://github.com/casey/just), and Python 3.9+ (for adapters).
```bash
-# Clone the repository
git clone https://github.com/spacedriveapp/spacedrive
cd spacedrive
-# Install dependencies
-bun install
-cargo run -p xtask -- setup # generates .cargo/config.toml with aliases
-cargo build # builds all core and apps (including the daemon and cli)
-
-# Copy dependencies into the debug Folder ( probably windows only )
-Copy-Item -Path "apps\.deps\lib\*.dll" -Destination "target\debug" -ErrorAction SilentlyContinue
-Copy-Item -Path "apps\.deps\bin\*.dll" -Destination "target\debug" -ErrorAction SilentlyContinue
-
-# Run the desktop app (automatically starts daemon)
-cd apps/tauri
-bun run tauri:dev
-```
-
-### Quick Start with CLI
-
-The CLI can manage libraries and run a persistent daemon that other apps connect to:
-
-```bash
-# Build and run the CLI
-cargo run -p sd-cli -- --help
-
-# Start the daemon (runs in background)
-cargo run -p sd-cli -- daemon start
-
-# Create a library
-cargo run -p sd-cli -- library create "My Library"
-
-# Add a location to index
-cargo run -p sd-cli -- location add ~/Documents
-
-# Search indexed files
-cargo run -p sd-cli -- search .
-
-# Now launch Tauri app - it will connect to the running daemon
-```
-
-### Running Tests
-
-Spacedrive has a comprehensive test suite covering single-device operations and multi-device networking scenarios.
-
-```bash
-# Run all tests
-cargo test --workspace
-
-# Run specific test
-cargo test test_device_pairing --nocapture
-
-# Run with detailed logging
-RUST_LOG=debug cargo test test_name --nocapture
-
-# Run core tests only
-cargo test -p sd-core
-```
-
-See the [Testing Guide](https://v2.spacedrive.com/core/testing) for detailed documentation on:
-
-- Integration test framework
-- Multi-device subprocess testing
-- Event monitoring patterns
-- Test helpers and utilities
-
-All integration tests are in `core/tests/` including device pairing, sync, file transfer, and job execution tests.
-
-### Development Commands
-
-```bash
-# Run all tests
-cargo test
-
-# Run tests for specific package
-cargo test -p sd-core
-
-# Build CLI in release mode
-cargo build -p sd-cli --release
-
-# Format code
-cargo fmt
-
-# Run lints
-cargo clippy
+just setup # bun install + native deps + cargo config
+just dev-desktop # launch the desktop app (auto-starts daemon)
+just test # run all workspace tests
```
---
## Privacy & Security
-Spacedrive is **local-first**. Your data stays on your devices.
+Spacedrive is local-first. Your data stays on your devices.
-- **End-to-End Encryption**: All P2P traffic encrypted via QUIC/TLS
-- **At-Rest Encryption**: Libraries can be encrypted on disk (SQLCipher)
-- **No Telemetry**: Zero tracking or analytics in the open source version
-- **Self-Hostable**: Run your own relay servers and cloud cores
-- **Data Sovereignty**: You control where your data lives
+- **End-to-End Encryption** — all P2P traffic encrypted via QUIC/TLS
+- **At-Rest Encryption** — libraries can be encrypted on disk (SQLCipher)
+- **No Telemetry** — zero tracking or analytics
+- **Self-Hostable** — run your own relay servers
+- **Data Sovereignty** — you control where your data lives
-Optional cloud integration (Spacedrive Cloud) is available for backup and remote access, but it's never required. The cloud service runs unmodified Spacedrive core as a standard P2P device—no special privileges, no custom APIs.
+Optional cloud integration is available for backup and remote access, but it's never required. The cloud service runs unmodified Spacedrive core as a standard P2P device—no special privileges.
---
-## Documentation
+## Contributing
-- **[v2 Documentation](https://v2.spacedrive.com)** - Complete guides and API reference
-- **[Self-Hosting Guide](https://v2.spacedrive.com/overview/self-hosting)** - Deploy Spacedrive server
-- **[Whitepaper](whitepaper/spacedrive.pdf)** - Technical architecture (work in progress)
-- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute
-- **[Architecture Docs](docs/core/architecture.md)** - Detailed system design
-- **[Extension SDK](docs/sdk.md)** - Build your own extensions
+- **Join [Discord](https://discord.gg/gTaF2Z44f5)** to chat with developers and community
+- **[Contributing Guide](CONTRIBUTING.md)**
+- **[Adapter Guide](docs/ADAPTERS.md)** — write a data source adapter
+- **[SpaceUI](https://github.com/spacedriveapp/spaceui)** — shared design system (clone alongside Spacedrive to work on UI)
---
-## Get Involved
+## License
-- **Star the repo** to support the project
-- **Join [Discord](https://discord.gg/gTaF2Z44f5)** to chat with developers and community
-- **Read the [v2 Documentation](https://v2.spacedrive.com)** for guides and API reference
-- **Read the [Whitepaper](whitepaper/spacedrive.pdf)** for the full technical vision
-- **Build an Extension** - Check out the [SDK docs](docs/sdk.md)
+FSL-1.1-ALv2 — [Functional Source License](https://fsl.software/), converting to Apache 2.0 after two years.
diff --git a/adapters/apple-notes/adapter.toml b/adapters/apple-notes/adapter.toml
new file mode 100644
index 000000000000..6f89a8ca97d2
--- /dev/null
+++ b/adapters/apple-notes/adapter.toml
@@ -0,0 +1,49 @@
+[adapter]
+id = "apple-notes"
+name = "Apple Notes"
+description = "Index notes from the Apple Notes app (macOS). Requires Full Disk Access for the running process."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "note"
+min_spacedrive = "0.1.0"
+trust_tier = "authored"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 120
+schedule = "*/10 * * * *"
+requires = ["python3 >= 3.9"]
+
+# No config fields — uses the standard macOS path automatically.
+# Full Disk Access must be granted to the process running Spacedrive.
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "note"
+name = "Note"
+icon = "note"
+
+[models.folder]
+fields.name = "string"
+fields.account = "string"
+
+[models.note]
+fields.title = "string"
+fields.body = "text"
+fields.snippet = "string"
+fields.created = "datetime"
+fields.modified = "datetime"
+fields.is_pinned = "boolean"
+
+[models.note.relations]
+belongs_to = ["folder"]
+
+[search]
+primary_model = "note"
+title = "title"
+preview = "body"
+subtitle = "snippet"
+search_fields = ["title", "body"]
+date_field = "modified"
diff --git a/adapters/apple-notes/icon.svg b/adapters/apple-notes/icon.svg
new file mode 100644
index 000000000000..d999bec2f747
--- /dev/null
+++ b/adapters/apple-notes/icon.svg
@@ -0,0 +1,14 @@
+
diff --git a/adapters/apple-notes/sync.py b/adapters/apple-notes/sync.py
new file mode 100644
index 000000000000..ff26a4d049d3
--- /dev/null
+++ b/adapters/apple-notes/sync.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+"""
+Apple Notes adapter for Spacedrive.
+
+Reads the Apple Notes SQLite database on macOS.
+Requires Full Disk Access for the running process.
+
+The database is at:
+ ~/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite
+
+Password-protected notes are skipped.
+Uses ZSNIPPET for note body text (plain text excerpt stored by Notes.app).
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone
+
+# Apple's Core Data epoch: 2001-01-01 00:00:00 UTC
+CORE_DATA_EPOCH = 978307200
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def core_data_time_to_iso(timestamp) -> str:
+ """Convert Core Data timestamp (seconds since 2001-01-01) to ISO 8601."""
+ try:
+ if timestamp is None or timestamp == 0:
+ return ""
+ unix_seconds = float(timestamp) + CORE_DATA_EPOCH
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError, TypeError):
+ return ""
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ # Standard macOS path
+ notes_db = os.path.expanduser(
+ "~/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
+ )
+
+ if not os.path.exists(notes_db):
+ log("error", f"Apple Notes database not found: {notes_db}")
+ sys.exit(2)
+
+ # Copy the database to avoid lock issues
+ tmp_db = tempfile.mktemp(suffix=".db")
+ try:
+ shutil.copy2(notes_db, tmp_db)
+ for ext in ["-wal", "-shm"]:
+ src = notes_db + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", "Permission denied. Grant Full Disk Access to the app running Spacedrive (System Settings > Privacy & Security > Full Disk Access)")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy Notes database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # ── Sync folders ──────────────────────────────────────────────────
+ folder_count = 0
+ folders = conn.execute("""
+ SELECT
+ f.Z_PK,
+ f.ZTITLE2 as name,
+ a.ZNAME as account_name,
+ f.ZIDENTIFIER as identifier
+ FROM ZICCLOUDSYNCINGOBJECT f
+ LEFT JOIN ZICCLOUDSYNCINGOBJECT a
+ ON f.ZACCOUNT4 = a.Z_PK
+ AND a.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICAccount')
+ WHERE f.ZTITLE2 IS NOT NULL
+ AND f.ZMARKEDFORDELETION != 1
+ AND f.ZIDENTIFIER IS NOT NULL
+ AND f.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICFolder')
+ """).fetchall()
+
+ folder_map = {}
+ for f in folders:
+ fid = f["identifier"]
+ folder_map[f["Z_PK"]] = fid
+ account = f["account_name"] or "Local"
+
+ emit({
+ "upsert": "folder",
+ "external_id": fid,
+ "fields": {
+ "name": f["name"] or "Untitled Folder",
+ "account": account,
+ }
+ })
+ folder_count += 1
+
+ log("info", f"Synced {folder_count} folders")
+
+ # ── Sync notes ────────────────────────────────────────────────────
+ note_count = 0
+ notes = conn.execute("""
+ SELECT
+ n.ZIDENTIFIER as identifier,
+ n.ZTITLE1 as title,
+ n.ZSNIPPET as snippet,
+ n.ZCREATIONDATE3 as created,
+ n.ZMODIFICATIONDATE1 as modified,
+ n.ZISPINNED as is_pinned,
+ n.ZFOLDER as folder_pk,
+ n.ZMARKEDFORDELETION as deleted
+ FROM ZICCLOUDSYNCINGOBJECT n
+ WHERE n.ZTITLE1 IS NOT NULL
+ AND (n.ZMARKEDFORDELETION IS NULL OR n.ZMARKEDFORDELETION != 1)
+ AND n.ZIDENTIFIER IS NOT NULL
+ AND n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')
+ """).fetchall()
+
+ for note in notes:
+ nid = note["identifier"]
+ title = note["title"] or "Untitled"
+ snippet = note["snippet"] or ""
+ created = core_data_time_to_iso(note["created"])
+ modified = core_data_time_to_iso(note["modified"])
+ is_pinned = bool(note["is_pinned"]) if note["is_pinned"] else False
+
+ # Use snippet as body (it's the plain text content Apple stores)
+ body = snippet
+
+ fields = {
+ "title": title,
+ "body": body,
+ "snippet": snippet[:500] if snippet else "",
+ "created": created,
+ "modified": modified,
+ "is_pinned": is_pinned,
+ }
+
+ # Set folder FK if we can resolve it
+ folder_pk = note["folder_pk"]
+ if folder_pk and folder_pk in folder_map:
+ fields["folder_id"] = folder_map[folder_pk]
+
+ emit({
+ "upsert": "note",
+ "external_id": nid,
+ "fields": fields,
+ })
+ note_count += 1
+
+ log("info", f"Synced {note_count} notes")
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/chrome-bookmarks/adapter.toml b/adapters/chrome-bookmarks/adapter.toml
new file mode 100644
index 000000000000..540f3e92bbc7
--- /dev/null
+++ b/adapters/chrome-bookmarks/adapter.toml
@@ -0,0 +1,51 @@
+[adapter]
+id = "chrome-bookmarks"
+name = "Chrome Bookmarks"
+description = "Index bookmarks from any Chromium-based browser (Chrome, Arc, Brave, Edge)"
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "bookmark"
+min_spacedrive = "0.1.0"
+trust_tier = "external"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 60
+schedule = "0 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "bookmarks_path"
+name = "Bookmarks File Path"
+description = "Path to the Chromium Bookmarks JSON file. Common locations: ~/Library/Application Support/Google/Chrome/Default/Bookmarks, ~/Library/Application Support/Arc/User Data/Default/Bookmarks, ~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Bookmarks"
+type = "string"
+required = true
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "bookmark"
+name = "Bookmark"
+icon = "bookmark"
+
+[models.folder]
+fields.name = "string"
+fields.path = "string"
+
+[models.bookmark]
+fields.title = "string"
+fields.url = "string"
+fields.date_added = "datetime"
+fields.folder_path = "string"
+
+[models.bookmark.relations]
+belongs_to = ["folder"]
+
+[search]
+primary_model = "bookmark"
+title = "title"
+preview = "url"
+subtitle = "folder_path"
+search_fields = ["title", "url"]
+date_field = "date_added"
diff --git a/adapters/chrome-bookmarks/icon.svg b/adapters/chrome-bookmarks/icon.svg
new file mode 100644
index 000000000000..4ff6ab6bab67
--- /dev/null
+++ b/adapters/chrome-bookmarks/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/adapters/chrome-bookmarks/sync.py b/adapters/chrome-bookmarks/sync.py
new file mode 100644
index 000000000000..74ac7e082e81
--- /dev/null
+++ b/adapters/chrome-bookmarks/sync.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+"""
+Chrome Bookmarks adapter for Spacedrive.
+
+Reads the Chromium Bookmarks JSON file (works with Chrome, Arc, Brave, Edge).
+Emits folders and bookmarks as JSONL operations.
+
+Full sync every time (file is small, no cursor needed).
+"""
+
+import json
+import sys
+import os
+from datetime import datetime, timezone
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def chromium_time_to_iso(timestamp_str: str) -> str:
+ """Convert Chromium timestamp (microseconds since 1601-01-01) to ISO 8601."""
+ try:
+ ts = int(timestamp_str)
+ if ts == 0:
+ return ""
+ # Chromium epoch: 1601-01-01 00:00:00 UTC
+ # Unix epoch offset: 11644473600 seconds
+ unix_seconds = (ts / 1_000_000) - 11644473600
+ if unix_seconds < 0:
+ return ""
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError):
+ return ""
+
+
+def process_node(node: dict, path: str = ""):
+ """Recursively process a bookmark tree node."""
+ node_type = node.get("type", "")
+ name = node.get("name", "")
+
+ if node_type == "folder":
+ folder_path = f"{path}/{name}" if path else name
+ folder_id = node.get("guid", node.get("id", name))
+
+ emit({
+ "upsert": "folder",
+ "external_id": folder_id,
+ "fields": {
+ "name": name,
+ "path": folder_path,
+ }
+ })
+
+ for child in node.get("children", []):
+ process_node(child, folder_path)
+
+ elif node_type == "url":
+ bookmark_id = node.get("guid", node.get("id", ""))
+ url = node.get("url", "")
+ title = name or url
+ date_added = chromium_time_to_iso(node.get("date_added", "0"))
+ folder_id = None
+
+ # Find parent folder ID from path
+ if path:
+ # The folder_id for belongs_to linking
+ pass
+
+ emit({
+ "upsert": "bookmark",
+ "external_id": bookmark_id,
+ "fields": {
+ "title": title,
+ "url": url,
+ "date_added": date_added,
+ "folder_path": path or "Uncategorized",
+ }
+ })
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ bookmarks_path = config.get("bookmarks_path", "")
+
+ if not bookmarks_path:
+ log("error", "Missing required config: bookmarks_path")
+ sys.exit(2)
+
+ bookmarks_path = os.path.expanduser(bookmarks_path)
+
+ if not os.path.exists(bookmarks_path):
+ log("error", f"Bookmarks file not found: {bookmarks_path}")
+ sys.exit(2)
+
+ try:
+ with open(bookmarks_path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ except Exception as e:
+ log("error", f"Failed to read bookmarks file: {e}")
+ sys.exit(2)
+
+ roots = data.get("roots", {})
+ total = 0
+
+ for root_name, root_node in roots.items():
+ if isinstance(root_node, dict) and root_node.get("type") == "folder":
+ process_node(root_node)
+
+ # Count what we emitted (approximate from the file)
+ def count_bookmarks(node):
+ c = 0
+ if node.get("type") == "url":
+ c = 1
+ for child in node.get("children", []):
+ c += count_bookmarks(child)
+ return c
+
+ for root_node in roots.values():
+ if isinstance(root_node, dict):
+ total += count_bookmarks(root_node)
+
+ log("info", f"Synced {total} bookmarks")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/chrome-history/adapter.toml b/adapters/chrome-history/adapter.toml
new file mode 100644
index 000000000000..f33d7e9505fb
--- /dev/null
+++ b/adapters/chrome-history/adapter.toml
@@ -0,0 +1,60 @@
+[adapter]
+id = "chrome-history"
+name = "Chrome History"
+description = "Index browsing history from any Chromium-based browser (Chrome, Arc, Brave, Edge)"
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "globe"
+min_spacedrive = "0.1.0"
+trust_tier = "external"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 120
+schedule = "*/15 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "history_path"
+name = "History Database Path"
+description = "Path to the Chromium History SQLite file. Common locations: ~/Library/Application Support/Google/Chrome/Default/History, ~/Library/Application Support/Arc/User Data/Default/History, ~/Library/Application Support/BraveSoftware/Brave-Browser/Default/History"
+type = "string"
+required = true
+
+[[adapter.config]]
+key = "min_visit_count"
+name = "Minimum Visit Count"
+description = "Only index URLs visited at least this many times (reduces noise)"
+type = "integer"
+required = false
+default = 1
+
+[[adapter.config]]
+key = "max_results"
+name = "Max Results"
+description = "Maximum URLs to index per sync"
+type = "integer"
+required = false
+default = 10000
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "history"
+name = "Browser History"
+icon = "globe"
+
+[models.page]
+fields.title = "string"
+fields.url = "string"
+fields.visit_count = "integer"
+fields.last_visit = "datetime"
+
+[search]
+primary_model = "page"
+title = "title"
+preview = "url"
+subtitle = "last_visit"
+search_fields = ["title", "url"]
+date_field = "last_visit"
diff --git a/adapters/chrome-history/icon.svg b/adapters/chrome-history/icon.svg
new file mode 100644
index 000000000000..4ff6ab6bab67
--- /dev/null
+++ b/adapters/chrome-history/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/adapters/chrome-history/sync.py b/adapters/chrome-history/sync.py
new file mode 100644
index 000000000000..5c09c36d0d14
--- /dev/null
+++ b/adapters/chrome-history/sync.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+"""
+Chrome History adapter for Spacedrive.
+
+Reads the Chromium History SQLite database (works with Chrome, Arc, Brave, Edge).
+Copies the DB to a temp file first since the browser holds a lock on it.
+
+Supports incremental sync via last_visit_time cursor.
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def chromium_time_to_iso(timestamp: int) -> str:
+ """Convert Chromium timestamp (microseconds since 1601-01-01) to ISO 8601."""
+ try:
+ if timestamp == 0:
+ return ""
+ unix_seconds = (timestamp / 1_000_000) - 11644473600
+ if unix_seconds < 0:
+ return ""
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError):
+ return ""
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ history_path = config.get("history_path", "")
+ if not history_path:
+ log("error", "Missing required config: history_path")
+ sys.exit(2)
+
+ history_path = os.path.expanduser(history_path)
+ if not os.path.exists(history_path):
+ log("error", f"History database not found: {history_path}")
+ sys.exit(2)
+
+ min_visit_count = int(config.get("min_visit_count", 1))
+ max_results = int(config.get("max_results", 10000))
+
+ # Copy the database since the browser holds a lock
+ tmp_db = tempfile.mktemp(suffix=".db")
+ try:
+ shutil.copy2(history_path, tmp_db)
+ # Also copy WAL if it exists
+ for ext in ["-wal", "-shm"]:
+ src = history_path + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", f"Permission denied reading: {history_path}")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy history database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # Build query
+ conditions = ["visit_count >= ?"]
+ params = [min_visit_count]
+
+ if cursor:
+ conditions.append("last_visit_time > ?")
+ params.append(int(cursor))
+
+ where = " AND ".join(conditions)
+ query = f"""
+ SELECT id, url, title, visit_count, last_visit_time
+ FROM urls
+ WHERE {where}
+ ORDER BY last_visit_time DESC
+ LIMIT ?
+ """
+ params.append(max_results)
+
+ rows = conn.execute(query, params).fetchall()
+
+ max_visit_time = int(cursor) if cursor else 0
+ count = 0
+
+ for row in rows:
+ url_id = str(row["id"])
+ url = row["url"] or ""
+ title = row["title"] or url
+ visit_count = row["visit_count"] or 0
+ last_visit = row["last_visit_time"] or 0
+
+ # Skip internal browser pages
+ if url.startswith(("chrome://", "chrome-extension://", "about:", "arc://", "brave://")):
+ continue
+
+ last_visit_iso = chromium_time_to_iso(last_visit)
+
+ emit({
+ "upsert": "page",
+ "external_id": url_id,
+ "fields": {
+ "title": title[:500],
+ "url": url[:2000],
+ "visit_count": visit_count,
+ "last_visit": last_visit_iso,
+ }
+ })
+ count += 1
+
+ if last_visit > max_visit_time:
+ max_visit_time = last_visit
+
+ # Emit cursor for incremental sync
+ if max_visit_time > 0:
+ emit({"cursor": str(max_visit_time)})
+
+ log("info", f"Synced {count} pages (min visits: {min_visit_count})")
+
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ # Cleanup temp files
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/github/adapter.toml b/adapters/github/adapter.toml
new file mode 100644
index 000000000000..61b04843d199
--- /dev/null
+++ b/adapters/github/adapter.toml
@@ -0,0 +1,97 @@
+[adapter]
+id = "github"
+name = "GitHub"
+description = "Index issues, pull requests, and discussions from GitHub repositories via the REST API. Requires a personal access token."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "git-branch"
+min_spacedrive = "0.1.0"
+trust_tier = "collaborative"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 600
+schedule = "*/30 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "token"
+name = "GitHub Token"
+description = "Personal access token with 'repo' scope (or fine-grained with Issues/PRs read access)"
+type = "string"
+required = true
+secret = true
+
+[[adapter.config]]
+key = "repos"
+name = "Repositories"
+description = "Comma-separated list of repos to index (e.g. 'owner/repo1, owner/repo2'). Leave blank to index all repos you have access to."
+type = "string"
+required = false
+
+[[adapter.config]]
+key = "include_prs"
+name = "Include Pull Requests"
+description = "Also index pull requests (not just issues)"
+type = "boolean"
+required = false
+default = true
+
+[[adapter.config]]
+key = "max_items"
+name = "Max Items per Repo"
+description = "Maximum issues/PRs to fetch per repository per sync"
+type = "integer"
+required = false
+default = 500
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "github-issue"
+name = "GitHub Issues & PRs"
+icon = "git-branch"
+
+[models.repository]
+fields.name = "string"
+fields.full_name = "string"
+fields.description = "text"
+fields.language = "string"
+fields.stars = "integer"
+fields.url = "string"
+has_many = ["issue"]
+
+[models.issue]
+fields.title = "string"
+fields.body = "text"
+fields.author = "string"
+fields.state = "string"
+fields.number = "integer"
+fields.url = "string"
+fields.is_pr = "boolean"
+fields.labels = "string"
+fields.comments_count = "integer"
+fields.created_at = "datetime"
+fields.updated_at = "datetime"
+fields.closed_at = "datetime"
+
+[models.issue.relations]
+belongs_to = ["repository"]
+
+[models.comment]
+fields.body = "text"
+fields.author = "string"
+fields.created_at = "datetime"
+fields.url = "string"
+
+[models.comment.relations]
+belongs_to = ["issue"]
+
+[search]
+primary_model = "issue"
+title = "title"
+preview = "body"
+subtitle = "author"
+search_fields = ["title", "body", "author", "labels"]
+date_field = "updated_at"
diff --git a/adapters/github/icon.svg b/adapters/github/icon.svg
new file mode 100644
index 000000000000..11678263c8df
--- /dev/null
+++ b/adapters/github/icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/adapters/github/sync.py b/adapters/github/sync.py
new file mode 100644
index 000000000000..821a5ef35b27
--- /dev/null
+++ b/adapters/github/sync.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python3
+"""
+GitHub adapter for Spacedrive.
+
+Indexes issues, pull requests, and comments from GitHub repositories
+using the REST API. Supports incremental sync via `updated_at` cursor.
+
+Requires a personal access token with `repo` scope (or fine-grained
+token with Issues and Pull Requests read permissions).
+"""
+
+import json
+import sys
+import urllib.request
+import urllib.error
+from datetime import datetime, timezone
+
+
+API_BASE = "https://api.github.com"
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def api_get(path: str, token: str, params: dict = None) -> list:
+ """Make a GET request to GitHub API. Handles pagination, returns all results."""
+ results = []
+ url = f"{API_BASE}{path}"
+
+ if params:
+ query_parts = []
+ for k, v in params.items():
+ query_parts.append(f"{k}={v}")
+ url += "?" + "&".join(query_parts)
+
+ page = 1
+ while True:
+ separator = "&" if "?" in url else "?"
+ page_url = f"{url}{separator}page={page}&per_page=100"
+
+ req = urllib.request.Request(page_url)
+ req.add_header("Authorization", f"Bearer {token}")
+ req.add_header("Accept", "application/vnd.github+json")
+ req.add_header("X-GitHub-Api-Version", "2022-11-28")
+ req.add_header("User-Agent", "spacedrive-adapter/0.1")
+
+ try:
+ with urllib.request.urlopen(req) as resp:
+ data = json.loads(resp.read().decode())
+ except urllib.error.HTTPError as e:
+ if e.code == 403:
+ # Rate limited — stop pagination
+ log("warn", f"GitHub API rate limited on {path}")
+ break
+ elif e.code == 404:
+ log("warn", f"Not found: {path}")
+ return []
+ else:
+ raise
+
+ if not isinstance(data, list):
+ # Single object response (e.g., /repos/owner/repo)
+ return [data]
+
+ results.extend(data)
+
+ if len(data) < 100:
+ break
+ page += 1
+
+ # Safety limit
+ if page > 50:
+ log("warn", f"Pagination limit reached for {path}")
+ break
+
+ return results
+
+
+def parse_iso(dt_str: str) -> str:
+ """Normalize GitHub's ISO 8601 timestamps to UTC."""
+ if not dt_str:
+ return ""
+ try:
+ # GitHub returns "2025-01-15T10:30:00Z" format
+ s = dt_str.replace("Z", "+00:00")
+ dt = datetime.fromisoformat(s)
+ return dt.astimezone(timezone.utc).isoformat()
+ except (ValueError, TypeError):
+ return dt_str
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ token = config.get("token", "")
+ if not token:
+ log("error", "Missing required config: token")
+ sys.exit(2)
+
+ repos_str = config.get("repos", "")
+ include_prs = config.get("include_prs", True)
+ if isinstance(include_prs, str):
+ include_prs = include_prs.lower() in ("true", "1", "yes")
+ max_items = int(config.get("max_items", 500))
+
+ # ── Determine repos to index ─────────────────────────────────────────
+ if repos_str:
+ repo_list = [r.strip() for r in repos_str.split(",") if r.strip()]
+ else:
+ # Fetch all repos the user has access to
+ log("info", "No repos specified, fetching all accessible repos")
+ try:
+ all_repos = api_get("/user/repos", token, {"sort": "updated", "type": "all"})
+ repo_list = [r["full_name"] for r in all_repos if r.get("full_name")]
+ except Exception as e:
+ log("error", f"Failed to fetch repos: {e}")
+ sys.exit(1)
+
+ log("info", f"Indexing {len(repo_list)} repositories")
+
+ max_updated = cursor or ""
+ total_issues = 0
+ total_comments = 0
+
+ for repo_full_name in repo_list:
+ # ── Upsert repository ────────────────────────────────────────────
+ try:
+ repo_data = api_get(f"/repos/{repo_full_name}", token)
+ if not repo_data:
+ log("warn", f"Could not fetch repo: {repo_full_name}")
+ continue
+ repo = repo_data[0]
+ except Exception as e:
+ log("warn", f"Failed to fetch repo {repo_full_name}: {e}")
+ continue
+
+ repo_id = str(repo.get("id", repo_full_name))
+
+ emit({
+ "upsert": "repository",
+ "external_id": repo_id,
+ "fields": {
+ "name": repo.get("name", ""),
+ "full_name": repo.get("full_name", repo_full_name),
+ "description": (repo.get("description") or "")[:5000],
+ "language": repo.get("language") or "",
+ "stars": repo.get("stargazers_count", 0),
+ "url": repo.get("html_url", ""),
+ }
+ })
+
+ # ── Fetch issues (and PRs if enabled) ────────────────────────────
+ params = {
+ "state": "all",
+ "sort": "updated",
+ "direction": "desc",
+ }
+ if cursor:
+ params["since"] = cursor
+
+ try:
+ issues = api_get(f"/repos/{repo_full_name}/issues", token, params)
+ except Exception as e:
+ log("warn", f"Failed to fetch issues for {repo_full_name}: {e}")
+ continue
+
+ repo_issue_count = 0
+
+ for item in issues:
+ if repo_issue_count >= max_items:
+ break
+
+ is_pr = "pull_request" in item
+ if is_pr and not include_prs:
+ continue
+
+ issue_number = item.get("number", 0)
+ issue_id = f"{repo_full_name}#{issue_number}"
+
+ title = item.get("title", "")
+ body = item.get("body") or ""
+ author = item.get("user", {}).get("login", "") if item.get("user") else ""
+ state = item.get("state", "")
+ url = item.get("html_url", "")
+ created_at = parse_iso(item.get("created_at", ""))
+ updated_at = parse_iso(item.get("updated_at", ""))
+ closed_at = parse_iso(item.get("closed_at", ""))
+ comments_count = item.get("comments", 0)
+
+ # Labels
+ labels = []
+ for label in item.get("labels", []):
+ if isinstance(label, dict):
+ labels.append(label.get("name", ""))
+ elif isinstance(label, str):
+ labels.append(label)
+ labels_str = ", ".join(labels)
+
+ emit({
+ "upsert": "issue",
+ "external_id": issue_id,
+ "fields": {
+ "title": title[:500],
+ "body": body[:50000],
+ "author": author,
+ "state": state,
+ "number": issue_number,
+ "url": url,
+ "is_pr": is_pr,
+ "labels": labels_str[:1000],
+ "comments_count": comments_count,
+ "created_at": created_at,
+ "updated_at": updated_at,
+ "closed_at": closed_at,
+ "repository_id": repo_id,
+ }
+ })
+ total_issues += 1
+ repo_issue_count += 1
+
+ # Track latest update for cursor
+ raw_updated = item.get("updated_at", "")
+ if raw_updated > max_updated:
+ max_updated = raw_updated
+
+ # ── Fetch comments for this issue ────────────────────────────
+ if comments_count > 0:
+ try:
+ comments = api_get(
+ f"/repos/{repo_full_name}/issues/{issue_number}/comments",
+ token
+ )
+ for comment in comments:
+ comment_id = str(comment.get("id", ""))
+ if not comment_id:
+ continue
+
+ emit({
+ "upsert": "comment",
+ "external_id": comment_id,
+ "fields": {
+ "body": (comment.get("body") or "")[:50000],
+ "author": comment.get("user", {}).get("login", "") if comment.get("user") else "",
+ "created_at": parse_iso(comment.get("created_at", "")),
+ "url": comment.get("html_url", ""),
+ "issue_id": issue_id,
+ }
+ })
+ total_comments += 1
+ except Exception as e:
+ log("warn", f"Failed to fetch comments for {issue_id}: {e}")
+
+ log("info", f"{repo_full_name}: {repo_issue_count} issues/PRs")
+
+ # Emit cursor
+ if max_updated:
+ emit({"cursor": max_updated})
+
+ log("info", f"Synced {total_issues} issues/PRs and {total_comments} comments from {len(repo_list)} repos")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/gmail/adapter.toml b/adapters/gmail/adapter.toml
new file mode 100644
index 000000000000..79858edd41a4
--- /dev/null
+++ b/adapters/gmail/adapter.toml
@@ -0,0 +1,119 @@
+[adapter]
+id = "gmail"
+name = "Gmail"
+description = "Index emails, threads, labels, and attachments from Gmail via the Gmail API"
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "mail"
+min_spacedrive = "0.1.0"
+trust_tier = "external"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 600
+schedule = "*/5 * * * *"
+requires = ["python3 >= 3.9"]
+
+[adapter.oauth]
+token_url = "https://oauth2.googleapis.com/token"
+scopes = [
+ "https://www.googleapis.com/auth/gmail.readonly",
+ "https://www.googleapis.com/auth/gmail.metadata",
+]
+token_key = "oauth_token"
+
+[[adapter.config]]
+key = "client_id"
+name = "Google Client ID"
+description = "OAuth2 client ID from Google Cloud Console"
+type = "string"
+required = true
+secret = true
+
+[[adapter.config]]
+key = "client_secret"
+name = "Google Client Secret"
+description = "OAuth2 client secret from Google Cloud Console"
+type = "string"
+required = true
+secret = true
+
+[[adapter.config]]
+key = "refresh_token"
+name = "Refresh Token"
+description = "OAuth2 refresh token obtained during initial authorization"
+type = "string"
+required = true
+secret = true
+
+[[adapter.config]]
+key = "email"
+name = "Email Address"
+description = "The Gmail address to index"
+type = "string"
+required = true
+
+[[adapter.config]]
+key = "labels"
+name = "Labels to Index"
+description = "Comma-separated list of labels to index (blank = all)"
+type = "string"
+required = false
+
+[[adapter.config]]
+key = "max_results"
+name = "Max Results per Sync"
+description = "Maximum messages to fetch per sync cycle"
+type = "integer"
+required = false
+default = 500
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "email"
+name = "Email"
+icon = "mail"
+
+[models.thread]
+fields.subject = "string"
+fields.last_date = "datetime"
+fields.message_count = "integer"
+fields.snippet = "text"
+has_many = ["message"]
+
+[models.message]
+fields.subject = "string"
+fields.body = "text"
+fields.from = "string"
+fields.to = "string"
+fields.cc = "string"
+fields.date = "datetime"
+fields.is_read = "boolean"
+fields.is_starred = "boolean"
+
+[models.message.relations]
+belongs_to = ["thread"]
+many_to_many = ["label"]
+
+[models.label]
+fields.name = "string"
+fields.color = "string"
+fields.type = "string"
+
+[models.attachment]
+fields.filename = "string"
+fields.mime_type = "string"
+fields.size = "integer"
+
+[models.attachment.relations]
+belongs_to = ["message"]
+
+[search]
+primary_model = "message"
+title = "subject"
+preview = "body"
+subtitle = "from"
+search_fields = ["subject", "body", "from", "to"]
+date_field = "date"
diff --git a/adapters/gmail/icon.svg b/adapters/gmail/icon.svg
new file mode 100644
index 000000000000..7f6de6b2e93d
--- /dev/null
+++ b/adapters/gmail/icon.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/adapters/gmail/sync.py b/adapters/gmail/sync.py
new file mode 100755
index 000000000000..33d4203703ef
--- /dev/null
+++ b/adapters/gmail/sync.py
@@ -0,0 +1,498 @@
+#!/usr/bin/env python3
+"""
+Gmail adapter for Spacedrive.
+
+Syncs threads, messages, labels, and attachments from Gmail via the Gmail API.
+Uses only Python stdlib (no pip dependencies).
+
+Protocol: reads JSON config from stdin, writes JSONL operations to stdout.
+
+Incremental sync: uses Gmail history ID as cursor. First sync fetches all
+messages; subsequent syncs fetch only changes since the last history ID.
+"""
+
+import json
+import sys
+import time
+import urllib.request
+import urllib.error
+import urllib.parse
+import base64
+import email
+import email.utils
+import email.header
+from datetime import datetime, timezone
+
+# ── Constants ────────────────────────────────────────────────────────────────
+
+GMAIL_API = "https://gmail.googleapis.com/gmail/v1"
+MAX_RETRIES = 3
+RETRY_DELAY = 2 # seconds, doubled on each retry
+BATCH_SIZE = 100 # messages per page
+
+# ── Helpers ──────────────────────────────────────────────────────────────────
+
+
+def log(level: str, message: str):
+ """Emit a log operation."""
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ """Emit a JSONL operation."""
+ print(json.dumps(operation), flush=True)
+
+
+def api_get(path: str, token: str, params: dict = None) -> dict:
+ """Make an authenticated GET request to the Gmail API with retries."""
+ url = f"{GMAIL_API}{path}"
+ if params:
+ url += "?" + urllib.parse.urlencode(params)
+
+ headers = {"Authorization": f"Bearer {token}"}
+ req = urllib.request.Request(url, headers=headers)
+
+ for attempt in range(MAX_RETRIES):
+ try:
+ with urllib.request.urlopen(req, timeout=30) as resp:
+ return json.loads(resp.read().decode("utf-8"))
+ except urllib.error.HTTPError as e:
+ if e.code == 429 or e.code >= 500:
+ delay = RETRY_DELAY * (2 ** attempt)
+ log("warn", f"API returned {e.code}, retrying in {delay}s...")
+ time.sleep(delay)
+ continue
+ elif e.code == 401:
+ log("error", "OAuth token expired or invalid (401 Unauthorized)")
+ sys.exit(2)
+ elif e.code == 403:
+ log("error", f"Access denied (403 Forbidden): {e.read().decode('utf-8', errors='replace')}")
+ sys.exit(2)
+ else:
+ raise
+ except urllib.error.URLError as e:
+ if attempt < MAX_RETRIES - 1:
+ delay = RETRY_DELAY * (2 ** attempt)
+ log("warn", f"Network error: {e}, retrying in {delay}s...")
+ time.sleep(delay)
+ continue
+ raise
+
+ log("error", f"Failed after {MAX_RETRIES} retries")
+ sys.exit(2)
+
+
+def decode_header(header_value: str) -> str:
+ """Decode a MIME-encoded email header."""
+ if not header_value:
+ return ""
+ decoded_parts = email.header.decode_header(header_value)
+ result = []
+ for part, charset in decoded_parts:
+ if isinstance(part, bytes):
+ result.append(part.decode(charset or "utf-8", errors="replace"))
+ else:
+ result.append(part)
+ return " ".join(result)
+
+
+def get_header(headers: list, name: str) -> str:
+ """Extract a header value from Gmail's header list."""
+ for h in headers:
+ if h.get("name", "").lower() == name.lower():
+ return h.get("value", "")
+ return ""
+
+
+def extract_body(payload: dict) -> str:
+ """Extract the plain text body from a Gmail message payload."""
+ mime_type = payload.get("mimeType", "")
+
+ # Direct text/plain
+ if mime_type == "text/plain":
+ data = payload.get("body", {}).get("data", "")
+ if data:
+ return base64.urlsafe_b64decode(data).decode("utf-8", errors="replace")
+
+ # Multipart — recurse
+ parts = payload.get("parts", [])
+ for part in parts:
+ part_mime = part.get("mimeType", "")
+ if part_mime == "text/plain":
+ data = part.get("body", {}).get("data", "")
+ if data:
+ return base64.urlsafe_b64decode(data).decode("utf-8", errors="replace")
+
+ # Fallback: try text/html
+ if mime_type == "text/html":
+ data = payload.get("body", {}).get("data", "")
+ if data:
+ html = base64.urlsafe_b64decode(data).decode("utf-8", errors="replace")
+ # Strip HTML tags (basic)
+ import re
+ return re.sub(r"<[^>]+>", "", html).strip()
+
+ for part in parts:
+ body = extract_body(part)
+ if body:
+ return body
+
+ return ""
+
+
+def extract_attachments(payload: dict, message_id: str) -> list:
+ """Extract attachment metadata from a Gmail message payload."""
+ attachments = []
+ parts = payload.get("parts", [])
+
+ for part in parts:
+ filename = part.get("filename", "")
+ if filename:
+ body = part.get("body", {})
+ attachments.append({
+ "filename": filename,
+ "mime_type": part.get("mimeType", "application/octet-stream"),
+ "size": body.get("size", 0),
+ })
+ # Recurse into nested parts
+ attachments.extend(extract_attachments(part, message_id))
+
+ return attachments
+
+
+def parse_date(date_str: str) -> str:
+ """Parse an email date header into ISO 8601 format, normalized to UTC."""
+ if not date_str:
+ return datetime.now(timezone.utc).isoformat()
+ try:
+ parsed = email.utils.parsedate_to_datetime(date_str)
+ return parsed.astimezone(timezone.utc).isoformat()
+ except Exception:
+ return datetime.now(timezone.utc).isoformat()
+
+
+# ── Sync Logic ───────────────────────────────────────────────────────────────
+
+
+def sync_labels(token: str, user: str, label_filter: list = None):
+ """Fetch and emit all Gmail labels."""
+ data = api_get(f"/users/{user}/labels", token)
+ labels = data.get("labels", [])
+
+ count = 0
+ for label in labels:
+ label_id = label["id"]
+
+ # Apply label filter if specified
+ if label_filter and label["name"] not in label_filter:
+ continue
+
+ # Get full label details
+ detail = api_get(f"/users/{user}/labels/{label_id}", token)
+
+ color_bg = ""
+ if "color" in detail:
+ color_bg = detail["color"].get("backgroundColor", "")
+
+ label_type = detail.get("type", "user").lower()
+
+ emit({
+ "upsert": "label",
+ "external_id": label_id,
+ "fields": {
+ "name": detail.get("name", label_id),
+ "color": color_bg,
+ "type": label_type,
+ }
+ })
+ count += 1
+
+ log("info", f"Synced {count} labels")
+ return count
+
+
+def sync_messages_full(token: str, user: str, max_results: int, label_filter: list = None):
+ """Full initial sync: fetch all messages."""
+ log("info", "Starting full sync...")
+
+ # Build query params
+ params = {"maxResults": min(BATCH_SIZE, max_results)}
+ if label_filter:
+ params["labelIds"] = ",".join(label_filter)
+
+ total_fetched = 0
+ threads_seen = set()
+ page_token = None
+
+ while total_fetched < max_results:
+ if page_token:
+ params["pageToken"] = page_token
+
+ # List messages
+ data = api_get(f"/users/{user}/messages", token, params)
+ messages = data.get("messages", [])
+
+ if not messages:
+ break
+
+ for msg_ref in messages:
+ if total_fetched >= max_results:
+ break
+
+ msg_id = msg_ref["id"]
+
+ # Fetch full message
+ msg = api_get(
+ f"/users/{user}/messages/{msg_id}",
+ token,
+ {"format": "full"}
+ )
+
+ process_message(msg, user, threads_seen)
+ total_fetched += 1
+
+ if total_fetched % 50 == 0:
+ log("info", f"Processed {total_fetched} messages...")
+
+ page_token = data.get("nextPageToken")
+ if not page_token:
+ break
+
+ log("info", f"Full sync complete: {total_fetched} messages, {len(threads_seen)} threads")
+ return total_fetched
+
+
+def sync_messages_incremental(token: str, user: str, history_id: str, max_results: int):
+ """Incremental sync: fetch changes since last history ID."""
+ log("info", f"Incremental sync from history ID {history_id}...")
+
+ params = {
+ "startHistoryId": history_id,
+ "maxResults": min(BATCH_SIZE, max_results),
+ "historyTypes": "messageAdded,messageDeleted,labelAdded,labelRemoved",
+ }
+
+ total_changes = 0
+ threads_seen = set()
+ page_token = None
+
+ while True:
+ if page_token:
+ params["pageToken"] = page_token
+
+ try:
+ data = api_get(f"/users/{user}/history", token, params)
+ except urllib.error.HTTPError as e:
+ if e.code == 404:
+ # History ID too old — need full sync
+ log("warn", "History ID expired, falling back to full sync")
+ return sync_messages_full(token, user, max_results)
+ raise
+
+ history = data.get("history", [])
+
+ for record in history:
+ # Messages added
+ for added in record.get("messagesAdded", []):
+ msg_ref = added.get("message", {})
+ msg_id = msg_ref.get("id")
+ if msg_id:
+ msg = api_get(
+ f"/users/{user}/messages/{msg_id}",
+ token,
+ {"format": "full"}
+ )
+ process_message(msg, user, threads_seen)
+ total_changes += 1
+
+ # Messages deleted
+ for deleted in record.get("messagesDeleted", []):
+ msg_ref = deleted.get("message", {})
+ msg_id = msg_ref.get("id")
+ if msg_id:
+ emit({"delete": "message", "external_id": msg_id})
+ total_changes += 1
+
+ # Label changes — re-fetch the message to update links
+ for label_change in record.get("labelsAdded", []) + record.get("labelsRemoved", []):
+ msg_ref = label_change.get("message", {})
+ msg_id = msg_ref.get("id")
+ if msg_id:
+ try:
+ msg = api_get(
+ f"/users/{user}/messages/{msg_id}",
+ token,
+ {"format": "metadata", "metadataHeaders": ""}
+ )
+ # Re-link labels
+ label_ids = msg.get("labelIds", [])
+ for label_id in label_ids:
+ emit({
+ "link": "message",
+ "id": msg_id,
+ "to": "label",
+ "to_id": label_id,
+ })
+ except Exception:
+ pass # message may have been deleted
+
+ page_token = data.get("nextPageToken")
+ if not page_token:
+ break
+
+ new_history_id = data.get("historyId", history_id)
+ log("info", f"Incremental sync complete: {total_changes} changes")
+
+ return total_changes, new_history_id
+
+
+def process_message(msg: dict, user: str, threads_seen: set):
+ """Process a single Gmail message: emit thread (if new), message, attachments, and label links."""
+ msg_id = msg["id"]
+ thread_id = msg.get("threadId", msg_id)
+ payload = msg.get("payload", {})
+ headers = payload.get("headers", [])
+
+ # Extract message fields
+ subject = decode_header(get_header(headers, "Subject"))
+ from_addr = get_header(headers, "From")
+ to_addr = get_header(headers, "To")
+ cc_addr = get_header(headers, "Cc")
+ date_str = get_header(headers, "Date")
+ date_iso = parse_date(date_str)
+
+ body = extract_body(payload)
+ # Truncate very long bodies for storage
+ if len(body) > 50000:
+ body = body[:50000] + "..."
+
+ label_ids = msg.get("labelIds", [])
+ is_read = "UNREAD" not in label_ids
+ is_starred = "STARRED" in label_ids
+
+ # Emit thread (upsert — first message wins for subject, subsequent updates are fine)
+ if thread_id not in threads_seen:
+ threads_seen.add(thread_id)
+ snippet = msg.get("snippet", "")
+ # The thread subject is typically the first message's subject
+ emit({
+ "upsert": "thread",
+ "external_id": thread_id,
+ "fields": {
+ "subject": subject,
+ "last_date": date_iso,
+ "message_count": 1,
+ "snippet": snippet,
+ }
+ })
+
+ # Emit message
+ emit({
+ "upsert": "message",
+ "external_id": msg_id,
+ "fields": {
+ "subject": subject,
+ "body": body,
+ "from": from_addr,
+ "to": to_addr,
+ "cc": cc_addr,
+ "date": date_iso,
+ "is_read": is_read,
+ "is_starred": is_starred,
+ "thread_id": thread_id,
+ }
+ })
+
+ # Emit attachments
+ attachments = extract_attachments(payload, msg_id)
+ for i, att in enumerate(attachments):
+ att_id = f"{msg_id}_att_{i}"
+ emit({
+ "upsert": "attachment",
+ "external_id": att_id,
+ "fields": {
+ "filename": att["filename"],
+ "mime_type": att["mime_type"],
+ "size": att["size"],
+ "message_id": msg_id,
+ }
+ })
+
+ # Link message to labels
+ for label_id in label_ids:
+ emit({
+ "link": "message",
+ "id": msg_id,
+ "to": "label",
+ "to_id": label_id,
+ })
+
+
+# ── Main ─────────────────────────────────────────────────────────────────────
+
+
+def main():
+ # Read input from Spacedrive
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ # Required config
+ oauth_token = config.get("oauth_token")
+ if not oauth_token:
+ log("error", "Missing required config: oauth_token")
+ sys.exit(2)
+
+ user_email = config.get("email", "me")
+ max_results = int(config.get("max_results", 500))
+
+ # Optional label filter
+ labels_str = config.get("labels", "")
+ label_filter = [l.strip() for l in labels_str.split(",") if l.strip()] if labels_str else None
+
+ # Use "me" as the Gmail user (authenticated user)
+ user = "me"
+
+ try:
+ # Step 1: Sync labels (always full)
+ sync_labels(oauth_token, user, label_filter)
+
+ # Step 2: Sync messages
+ if cursor:
+ # Incremental sync
+ total, new_history_id = sync_messages_incremental(
+ oauth_token, user, cursor, max_results
+ )
+ # Emit new cursor
+ emit({"cursor": str(new_history_id)})
+ else:
+ # Full sync — get current profile for history ID
+ profile = api_get(f"/users/{user}/profile", oauth_token)
+ current_history_id = profile.get("historyId", "")
+
+ total = sync_messages_full(
+ oauth_token, user, max_results, label_filter
+ )
+
+ # Set cursor to current history ID for next incremental sync
+ if current_history_id:
+ emit({"cursor": str(current_history_id)})
+
+ log("info", f"Sync complete: {total} messages processed")
+
+ except urllib.error.HTTPError as e:
+ body = e.read().decode("utf-8", errors="replace") if hasattr(e, "read") else str(e)
+ log("error", f"Gmail API error {e.code}: {body}")
+ sys.exit(1) # Partial failure — some records may have been written
+ except Exception as e:
+ log("error", f"Unexpected error: {e}")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/macos-calendar/adapter.toml b/adapters/macos-calendar/adapter.toml
new file mode 100644
index 000000000000..b736bebbe9cd
--- /dev/null
+++ b/adapters/macos-calendar/adapter.toml
@@ -0,0 +1,60 @@
+[adapter]
+id = "macos-calendar"
+name = "macOS Calendar"
+description = "Index events from the macOS Calendar app. Requires Calendar access permission (or Full Disk Access) for the running process."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "calendar"
+min_spacedrive = "0.1.0"
+trust_tier = "authored"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 120
+schedule = "0 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "months_back"
+name = "Months of History"
+description = "How many months of past events to index (0 = all)"
+type = "integer"
+required = false
+default = 12
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "calendar-event"
+name = "Calendar Event"
+icon = "calendar"
+
+[models.calendar]
+fields.name = "string"
+fields.color = "string"
+fields.account = "string"
+has_many = ["event"]
+
+[models.event]
+fields.title = "string"
+fields.location = "string"
+fields.notes = "text"
+fields.start_date = "datetime"
+fields.end_date = "datetime"
+fields.is_all_day = "boolean"
+fields.recurrence = "string"
+fields.attendees = "text"
+fields.url = "string"
+fields.status = "string"
+
+[models.event.relations]
+belongs_to = ["calendar"]
+
+[search]
+primary_model = "event"
+title = "title"
+preview = "notes"
+subtitle = "location"
+search_fields = ["title", "location", "notes", "attendees"]
+date_field = "start_date"
diff --git a/adapters/macos-calendar/icon.svg b/adapters/macos-calendar/icon.svg
new file mode 100644
index 000000000000..6e961c5f98fd
--- /dev/null
+++ b/adapters/macos-calendar/icon.svg
@@ -0,0 +1,7 @@
+
diff --git a/adapters/macos-calendar/sync.py b/adapters/macos-calendar/sync.py
new file mode 100644
index 000000000000..d8f763f2b018
--- /dev/null
+++ b/adapters/macos-calendar/sync.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+"""
+macOS Calendar adapter for Spacedrive.
+
+Reads the macOS Calendar SQLite database (CalendarAgent).
+Located at ~/Library/Calendars/Calendar.sqlitedb
+
+Requires Calendar access (or Full Disk Access) for the running process.
+
+Incremental sync via modification date cursor.
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone, timedelta
+
+# Core Data epoch: 2001-01-01 00:00:00 UTC
+CORE_DATA_EPOCH = 978307200
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def cd_time_to_iso(timestamp) -> str:
+ """Convert Core Data timestamp (seconds since 2001-01-01) to ISO 8601 UTC."""
+ try:
+ if timestamp is None or timestamp == 0:
+ return ""
+ unix_seconds = float(timestamp) + CORE_DATA_EPOCH
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError, TypeError):
+ return ""
+
+
+def find_calendar_db() -> str:
+ """Find the Calendar database path."""
+ # Primary location
+ primary = os.path.expanduser("~/Library/Calendars/Calendar.sqlitedb")
+ if os.path.exists(primary):
+ return primary
+
+ # Some macOS versions use a different path
+ alt = os.path.expanduser("~/Library/Group Containers/group.com.apple.CalendarAgent/Calendar.sqlitedb")
+ if os.path.exists(alt):
+ return alt
+
+ return ""
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ months_back = int(config.get("months_back", 12))
+
+ db_path = find_calendar_db()
+ if not db_path:
+ log("error", "macOS Calendar database not found. Ensure Calendar access is granted.")
+ sys.exit(2)
+
+ # Copy the database since CalendarAgent holds a lock
+ tmp_db = tempfile.mktemp(suffix=".sqlitedb")
+ try:
+ shutil.copy2(db_path, tmp_db)
+ for ext in ["-wal", "-shm"]:
+ src = db_path + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", f"Permission denied reading: {db_path}. Grant Calendar or Full Disk Access.")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy Calendar database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # ── Load calendars ───────────────────────────────────────────────
+ calendars = {}
+ try:
+ cal_rows = conn.execute("""
+ SELECT ROWID, ZTITLE, ZCOLOR, ZSOURCEACCOUNT
+ FROM ZCALENDAR
+ WHERE ZTITLE IS NOT NULL
+ """).fetchall()
+
+ for cal in cal_rows:
+ cal_id = str(cal["ROWID"])
+ cal_name = cal["ZTITLE"] or ""
+ # Color is stored as an integer in some schemas
+ color_raw = cal["ZCOLOR"]
+ color = str(color_raw) if color_raw else ""
+
+ # Try to get account name
+ account = ""
+ try:
+ if cal["ZSOURCEACCOUNT"]:
+ acct_row = conn.execute(
+ "SELECT ZACCOUNTNAME FROM ZSOURCE WHERE ROWID = ?",
+ [cal["ZSOURCEACCOUNT"]]
+ ).fetchone()
+ if acct_row:
+ account = acct_row["ZACCOUNTNAME"] or ""
+ except sqlite3.Error:
+ pass
+
+ calendars[cal["ROWID"]] = cal_name
+
+ emit({
+ "upsert": "calendar",
+ "external_id": cal_id,
+ "fields": {
+ "name": cal_name,
+ "color": color,
+ "account": account,
+ }
+ })
+ except sqlite3.Error as e:
+ log("warn", f"Could not read calendars: {e}")
+
+ # ── Load events ──────────────────────────────────────────────────
+ conditions = []
+ params = []
+
+ # Date range filter
+ if months_back > 0:
+ cutoff = datetime.now(timezone.utc) - timedelta(days=months_back * 30)
+ cutoff_cd = cutoff.timestamp() - CORE_DATA_EPOCH
+ conditions.append("ZSTARTDATE >= ?")
+ params.append(cutoff_cd)
+
+ if cursor:
+ conditions.append("ZLASTMODIFIEDDATE > ?")
+ params.append(float(cursor))
+
+ where = " AND ".join(conditions) if conditions else "1=1"
+
+ events = conn.execute(f"""
+ SELECT
+ ROWID,
+ ZSUMMARY,
+ ZLOCATION,
+ ZNOTES,
+ ZSTARTDATE,
+ ZENDDATE,
+ ZISALLDAY,
+ ZRECURRENCERULE,
+ ZURL,
+ ZSTATUS,
+ ZCALENDAR,
+ ZLASTMODIFIEDDATE
+ FROM ZCALENDARITEM
+ WHERE ZENTITYTYPE = 1 AND {where}
+ ORDER BY ZSTARTDATE DESC
+ """, params).fetchall()
+
+ max_mod_date = float(cursor) if cursor else 0
+ count = 0
+
+ for event in events:
+ rowid = event["ROWID"]
+ title = event["ZSUMMARY"] or "(No Title)"
+ location = event["ZLOCATION"] or ""
+ notes = event["ZNOTES"] or ""
+ start_date = cd_time_to_iso(event["ZSTARTDATE"])
+ end_date = cd_time_to_iso(event["ZENDDATE"])
+ is_all_day = bool(event["ZISALLDAY"])
+ recurrence = str(event["ZRECURRENCERULE"] or "")
+ url = event["ZURL"] or ""
+ status_code = event["ZSTATUS"]
+ cal_rowid = event["ZCALENDAR"]
+ mod_date = event["ZLASTMODIFIEDDATE"] or 0
+
+ # Map status codes
+ status_map = {0: "none", 1: "confirmed", 2: "tentative", 3: "cancelled"}
+ status = status_map.get(status_code, "unknown") if status_code is not None else ""
+
+ # Fetch attendees
+ attendees = []
+ try:
+ att_rows = conn.execute("""
+ SELECT ZCOMMONNAME, ZADDRESS
+ FROM ZATTENDEE WHERE ZEVENT = ?
+ """, [rowid]).fetchall()
+ for att in att_rows:
+ name = att["ZCOMMONNAME"] or ""
+ addr = att["ZADDRESS"] or ""
+ if addr.startswith("mailto:"):
+ addr = addr[7:]
+ if name and addr:
+ attendees.append(f"{name} <{addr}>")
+ elif name:
+ attendees.append(name)
+ elif addr:
+ attendees.append(addr)
+ except sqlite3.Error:
+ pass
+
+ event_fields = {
+ "title": title[:500],
+ "location": location[:500],
+ "notes": notes[:10000],
+ "start_date": start_date,
+ "end_date": end_date,
+ "is_all_day": is_all_day,
+ "recurrence": recurrence[:200],
+ "attendees": "\n".join(attendees)[:5000],
+ "url": url[:2000],
+ "status": status,
+ }
+
+ # Add calendar FK if we know the calendar
+ if cal_rowid and cal_rowid in calendars:
+ event_fields["calendar_id"] = str(cal_rowid)
+
+ emit({
+ "upsert": "event",
+ "external_id": str(rowid),
+ "fields": event_fields,
+ })
+ count += 1
+
+ if mod_date > max_mod_date:
+ max_mod_date = mod_date
+
+ # Emit cursor
+ if max_mod_date > 0:
+ emit({"cursor": str(max_mod_date)})
+
+ log("info", f"Synced {count} events from {len(calendars)} calendars")
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/macos-contacts/adapter.toml b/adapters/macos-contacts/adapter.toml
new file mode 100644
index 000000000000..4715c4dc518f
--- /dev/null
+++ b/adapters/macos-contacts/adapter.toml
@@ -0,0 +1,52 @@
+[adapter]
+id = "macos-contacts"
+name = "macOS Contacts"
+description = "Index contacts from the macOS Address Book. Requires Contacts access permission for the running process."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "user"
+min_spacedrive = "0.1.0"
+trust_tier = "authored"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 120
+schedule = "0 * * * *"
+requires = ["python3 >= 3.9"]
+
+# No config fields — uses the standard macOS AddressBook database.
+# Contacts permission must be granted to the process running Spacedrive.
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "contact"
+name = "Contact"
+icon = "user"
+
+[models.group]
+fields.name = "string"
+
+[models.contact]
+fields.name = "string"
+fields.organization = "string"
+fields.job_title = "string"
+fields.emails = "string"
+fields.phones = "string"
+fields.addresses = "text"
+fields.notes = "text"
+fields.birthday = "string"
+fields.created = "datetime"
+fields.modified = "datetime"
+
+[models.contact.relations]
+many_to_many = ["group"]
+
+[search]
+primary_model = "contact"
+title = "name"
+preview = "notes"
+subtitle = "organization"
+search_fields = ["name", "organization", "job_title", "emails", "phones", "notes"]
+date_field = "modified"
diff --git a/adapters/macos-contacts/icon.svg b/adapters/macos-contacts/icon.svg
new file mode 100644
index 000000000000..087c68a63eda
--- /dev/null
+++ b/adapters/macos-contacts/icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/adapters/macos-contacts/sync.py b/adapters/macos-contacts/sync.py
new file mode 100644
index 000000000000..eba5e15125ab
--- /dev/null
+++ b/adapters/macos-contacts/sync.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+"""
+macOS Contacts adapter for Spacedrive.
+
+Reads the macOS AddressBook SQLite database directly.
+The database is at ~/Library/Application Support/AddressBook/AddressBook-v22.abcddb
+
+Requires Contacts permission for the running process.
+
+Full-scan adapter — re-reads all contacts each sync and upserts.
+Incremental via modification date cursor.
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone
+
+# Core Data epoch: 2001-01-01 00:00:00 UTC
+CORE_DATA_EPOCH = 978307200
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def cd_time_to_iso(timestamp) -> str:
+ """Convert Core Data timestamp (seconds since 2001-01-01) to ISO 8601 UTC."""
+ try:
+ if timestamp is None or timestamp == 0:
+ return ""
+ unix_seconds = float(timestamp) + CORE_DATA_EPOCH
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError, TypeError):
+ return ""
+
+
+def find_addressbook_db() -> str:
+ """Find the AddressBook database path."""
+ base = os.path.expanduser("~/Library/Application Support/AddressBook")
+
+ # Modern macOS: Sources//AddressBook-v22.abcddb
+ sources_dir = os.path.join(base, "Sources")
+ if os.path.isdir(sources_dir):
+ for entry in os.listdir(sources_dir):
+ candidate = os.path.join(sources_dir, entry, "AddressBook-v22.abcddb")
+ if os.path.exists(candidate):
+ return candidate
+
+ # Legacy location
+ legacy = os.path.join(base, "AddressBook-v22.abcddb")
+ if os.path.exists(legacy):
+ return legacy
+
+ return ""
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ db_path = find_addressbook_db()
+ if not db_path:
+ log("error", "macOS AddressBook database not found. Ensure Contacts permission is granted.")
+ sys.exit(2)
+
+ # Copy the database since Contacts may hold a lock
+ tmp_db = tempfile.mktemp(suffix=".abcddb")
+ try:
+ shutil.copy2(db_path, tmp_db)
+ for ext in ["-wal", "-shm"]:
+ src = db_path + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", f"Permission denied reading: {db_path}. Grant Contacts access.")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy AddressBook database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # ── Load groups ──────────────────────────────────────────────────
+ try:
+ groups = conn.execute(
+ "SELECT ROWID, ZNAME FROM ZABCDGROUP WHERE ZNAME IS NOT NULL"
+ ).fetchall()
+ for g in groups:
+ emit({
+ "upsert": "group",
+ "external_id": str(g["ROWID"]),
+ "fields": {"name": g["ZNAME"] or ""},
+ })
+ except sqlite3.Error:
+ log("warn", "Could not read groups table")
+
+ # ── Load group memberships ───────────────────────────────────────
+ group_members = {} # contact_rowid -> [group_rowid, ...]
+ try:
+ memberships = conn.execute("""
+ SELECT ZGROUP, ZMEMBER FROM ZABCDGROUPMEMBERS
+ """).fetchall()
+ for m in memberships:
+ group_id = m["ZGROUP"]
+ member_id = m["ZMEMBER"]
+ if member_id not in group_members:
+ group_members[member_id] = []
+ group_members[member_id].append(group_id)
+ except sqlite3.Error:
+ pass
+
+ # ── Load contacts ────────────────────────────────────────────────
+ conditions = ["1=1"]
+ params = []
+
+ if cursor:
+ conditions.append("ZMODIFICATIONDATE > ?")
+ params.append(float(cursor))
+
+ where = " AND ".join(conditions)
+
+ contacts = conn.execute(f"""
+ SELECT
+ ROWID,
+ ZFIRSTNAME,
+ ZLASTNAME,
+ ZORGANIZATION,
+ ZJOBTITLE,
+ ZNOTE,
+ ZBIRTHDAY,
+ ZCREATIONDATE,
+ ZMODIFICATIONDATE
+ FROM ZABCDRECORD
+ WHERE ZENTITYNAME = 'ABPerson' AND {where}
+ ORDER BY ZMODIFICATIONDATE DESC
+ """, params).fetchall()
+
+ max_mod_date = float(cursor) if cursor else 0
+ count = 0
+
+ for contact in contacts:
+ rowid = contact["ROWID"]
+ first = contact["ZFIRSTNAME"] or ""
+ last = contact["ZLASTNAME"] or ""
+ name = f"{first} {last}".strip() or "(No Name)"
+ org = contact["ZORGANIZATION"] or ""
+ job_title = contact["ZJOBTITLE"] or ""
+ notes = contact["ZNOTE"] or ""
+ birthday_raw = contact["ZBIRTHDAY"]
+ created = cd_time_to_iso(contact["ZCREATIONDATE"])
+ modified = cd_time_to_iso(contact["ZMODIFICATIONDATE"])
+ mod_date = contact["ZMODIFICATIONDATE"] or 0
+
+ birthday = ""
+ if birthday_raw:
+ birthday = cd_time_to_iso(birthday_raw)
+
+ # Fetch emails
+ emails = []
+ try:
+ email_rows = conn.execute(
+ "SELECT ZADDRESS FROM ZABCDEMAILADDRESS WHERE ZOWNER = ?",
+ [rowid]
+ ).fetchall()
+ emails = [r["ZADDRESS"] for r in email_rows if r["ZADDRESS"]]
+ except sqlite3.Error:
+ pass
+
+ # Fetch phone numbers
+ phones = []
+ try:
+ phone_rows = conn.execute(
+ "SELECT ZFULLNUMBER FROM ZABCDPHONENUMBER WHERE ZOWNER = ?",
+ [rowid]
+ ).fetchall()
+ phones = [r["ZFULLNUMBER"] for r in phone_rows if r["ZFULLNUMBER"]]
+ except sqlite3.Error:
+ pass
+
+ # Fetch addresses
+ addresses = []
+ try:
+ addr_rows = conn.execute("""
+ SELECT ZSTREET, ZCITY, ZSTATE, ZZIPCODE, ZCOUNTRYNAME
+ FROM ZABCDPOSTALADDRESS WHERE ZOWNER = ?
+ """, [rowid]).fetchall()
+ for a in addr_rows:
+ parts = [
+ a["ZSTREET"] or "",
+ a["ZCITY"] or "",
+ a["ZSTATE"] or "",
+ a["ZZIPCODE"] or "",
+ a["ZCOUNTRYNAME"] or "",
+ ]
+ addr_str = ", ".join(p for p in parts if p)
+ if addr_str:
+ addresses.append(addr_str)
+ except sqlite3.Error:
+ pass
+
+ emit({
+ "upsert": "contact",
+ "external_id": str(rowid),
+ "fields": {
+ "name": name,
+ "organization": org,
+ "job_title": job_title,
+ "emails": ", ".join(emails),
+ "phones": ", ".join(phones),
+ "addresses": "\n".join(addresses),
+ "notes": notes[:10000],
+ "birthday": birthday,
+ "created": created,
+ "modified": modified,
+ }
+ })
+ count += 1
+
+ # Link to groups
+ if rowid in group_members:
+ for gid in group_members[rowid]:
+ emit({
+ "link": "contact",
+ "id": str(rowid),
+ "to": "group",
+ "to_id": str(gid),
+ })
+
+ if mod_date > max_mod_date:
+ max_mod_date = mod_date
+
+ # Emit cursor
+ if max_mod_date > 0:
+ emit({"cursor": str(max_mod_date)})
+
+ log("info", f"Synced {count} contacts")
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/obsidian/adapter.toml b/adapters/obsidian/adapter.toml
new file mode 100644
index 000000000000..d174bd19159c
--- /dev/null
+++ b/adapters/obsidian/adapter.toml
@@ -0,0 +1,57 @@
+[adapter]
+id = "obsidian"
+name = "Obsidian Vault"
+description = "Index markdown notes from an Obsidian vault or any directory of .md files"
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "note"
+min_spacedrive = "0.1.0"
+trust_tier = "authored"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 300
+schedule = "*/10 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "vault_path"
+name = "Vault Path"
+description = "Path to the Obsidian vault directory (or any folder containing .md files)"
+type = "string"
+required = true
+
+[[adapter.config]]
+key = "exclude_patterns"
+name = "Exclude Patterns"
+description = "Comma-separated glob patterns to exclude (e.g., .obsidian,templates,daily)"
+type = "string"
+required = false
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "markdown"
+name = "Markdown Note"
+icon = "note"
+
+[models.note]
+fields.title = "string"
+fields.body = "text"
+fields.path = "string"
+fields.tags = "string"
+fields.created = "datetime"
+fields.modified = "datetime"
+fields.word_count = "integer"
+
+[models.note.relations]
+many_to_many = ["note"]
+
+[search]
+primary_model = "note"
+title = "title"
+preview = "body"
+subtitle = "path"
+search_fields = ["title", "body", "tags"]
+date_field = "modified"
diff --git a/adapters/obsidian/icon.svg b/adapters/obsidian/icon.svg
new file mode 100644
index 000000000000..060df503479e
--- /dev/null
+++ b/adapters/obsidian/icon.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/adapters/obsidian/sync.py b/adapters/obsidian/sync.py
new file mode 100644
index 000000000000..8766543e87bd
--- /dev/null
+++ b/adapters/obsidian/sync.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+"""
+Obsidian/Markdown vault adapter for Spacedrive.
+
+Recursively scans a directory for .md files, extracts YAML frontmatter
+and body text, and emits notes. Detects [[wikilinks]] between notes and
+creates many_to_many links.
+
+Incremental sync via file modification time cursor.
+"""
+
+import json
+import sys
+import os
+import re
+import fnmatch
+import hashlib
+from datetime import datetime, timezone
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def normalize_date(value: str, fallback: str) -> str:
+ """Try to parse an arbitrary date string into UTC ISO 8601.
+
+ Handles common frontmatter formats:
+ - 2025-01-15T10:30:00Z
+ - 2025-01-15T10:30:00+05:00
+ - 2025-01-15T10:30:00
+ - 2025-01-15 10:30:00
+ - 2025-01-15
+
+ Falls back to *fallback* (which should already be UTC ISO 8601) if parsing
+ fails, so we never store unparseable user strings.
+ """
+ if not value:
+ return fallback
+ s = str(value).strip()
+ # Try datetime.fromisoformat (Python 3.11+ accepts Z, earlier versions don't)
+ for candidate in (s, s.replace("Z", "+00:00")):
+ try:
+ dt = datetime.fromisoformat(candidate)
+ if dt.tzinfo is None:
+ dt = dt.replace(tzinfo=timezone.utc)
+ return dt.astimezone(timezone.utc).isoformat()
+ except (ValueError, TypeError):
+ continue
+ # Try date-only: YYYY-MM-DD
+ try:
+ dt = datetime.strptime(s[:10], "%Y-%m-%d").replace(tzinfo=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, TypeError):
+ pass
+ return fallback
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def parse_frontmatter(content: str):
+ """Extract YAML frontmatter and body from markdown content."""
+ frontmatter = {}
+ body = content
+
+ if content.startswith("---"):
+ parts = content.split("---", 2)
+ if len(parts) >= 3:
+ yaml_str = parts[1].strip()
+ body = parts[2].strip()
+
+ # Simple YAML key: value parser (no dependency on pyyaml)
+ for line in yaml_str.split("\n"):
+ line = line.strip()
+ if ":" in line and not line.startswith("#"):
+ key, _, value = line.partition(":")
+ key = key.strip()
+ value = value.strip()
+ # Handle lists
+ if value.startswith("[") and value.endswith("]"):
+ items = [
+ v.strip().strip("'\"")
+ for v in value[1:-1].split(",")
+ if v.strip()
+ ]
+ frontmatter[key] = items
+ elif value.startswith("'") or value.startswith('"'):
+ frontmatter[key] = value.strip("'\"")
+ else:
+ frontmatter[key] = value
+
+ return frontmatter, body
+
+
+def extract_wikilinks(body: str) -> list:
+ """Extract [[wikilink]] targets from markdown body."""
+ # Match [[Page Name]] and [[Page Name|Display Text]]
+ pattern = r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]"
+ matches = re.findall(pattern, body)
+ # Normalize: strip whitespace, convert to lowercase for matching
+ return list(set(m.strip() for m in matches))
+
+
+def file_id(path: str, vault_root: str) -> str:
+ """Generate a stable ID from the relative path."""
+ rel = os.path.relpath(path, vault_root)
+ return hashlib.sha256(rel.encode("utf-8")).hexdigest()[:16]
+
+
+def should_exclude(rel_path: str, patterns: list) -> bool:
+ """Check if a relative path matches any exclude pattern."""
+ parts = rel_path.split(os.sep)
+ for pattern in patterns:
+ pattern = pattern.strip()
+ if not pattern:
+ continue
+ # Match against directory components and filename
+ for part in parts:
+ if fnmatch.fnmatch(part, pattern):
+ return True
+ if fnmatch.fnmatch(rel_path, pattern):
+ return True
+ return False
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ vault_path = config.get("vault_path", "")
+ if not vault_path:
+ log("error", "Missing required config: vault_path")
+ sys.exit(2)
+
+ vault_path = os.path.expanduser(vault_path)
+ if not os.path.isdir(vault_path):
+ log("error", f"Vault directory not found: {vault_path}")
+ sys.exit(2)
+
+ # Parse exclude patterns
+ exclude_str = config.get("exclude_patterns", "")
+ exclude_patterns = [p.strip() for p in exclude_str.split(",") if p.strip()] if exclude_str else []
+ # Always exclude hidden dirs
+ exclude_patterns.extend([".obsidian", ".git", ".trash", "node_modules"])
+
+ # Parse cursor (last sync timestamp as float)
+ last_sync = float(cursor) if cursor else 0
+
+ # Collect all markdown files
+ md_files = []
+ for root, dirs, files in os.walk(vault_path):
+ # Skip excluded directories
+ dirs[:] = [d for d in dirs if not should_exclude(d, exclude_patterns)]
+
+ for fname in files:
+ if not fname.endswith(".md"):
+ continue
+
+ full_path = os.path.join(root, fname)
+ rel_path = os.path.relpath(full_path, vault_path)
+
+ if should_exclude(rel_path, exclude_patterns):
+ continue
+
+ mtime = os.path.getmtime(full_path)
+
+ # For incremental sync, only process modified files
+ if last_sync > 0 and mtime <= last_sync:
+ continue
+
+ md_files.append((full_path, rel_path, mtime))
+
+ log("info", f"Found {len(md_files)} markdown files to process")
+
+ # First pass: emit all notes
+ note_links = {} # note_id -> [wikilink_targets]
+ title_to_id = {} # lowercase title -> note_id (for link resolution)
+ max_mtime = last_sync
+ count = 0
+
+ for full_path, rel_path, mtime in md_files:
+ try:
+ with open(full_path, "r", encoding="utf-8", errors="replace") as f:
+ content = f.read()
+ except Exception as e:
+ log("warn", f"Failed to read {rel_path}: {e}")
+ continue
+
+ frontmatter, body = parse_frontmatter(content)
+
+ # Derive title from filename (without .md)
+ title = os.path.splitext(os.path.basename(full_path))[0]
+
+ # Extract metadata
+ tags_raw = frontmatter.get("tags", [])
+ if isinstance(tags_raw, list):
+ tags = ", ".join(str(t) for t in tags_raw)
+ else:
+ tags = str(tags_raw)
+
+ # Also extract #tags from body
+ body_tags = re.findall(r"(?:^|\s)#([a-zA-Z][a-zA-Z0-9_/-]*)", body)
+ if body_tags:
+ all_tags = set(t.strip() for t in tags.split(",") if t.strip())
+ all_tags.update(body_tags)
+ tags = ", ".join(sorted(all_tags))
+
+ word_count = len(body.split())
+
+ # Dates — normalize to UTC ISO 8601 for consistent temporal queries
+ ctime = os.path.getctime(full_path)
+ ctime_iso = datetime.fromtimestamp(ctime, tz=timezone.utc).isoformat()
+ modified = datetime.fromtimestamp(mtime, tz=timezone.utc).isoformat()
+
+ raw_created = frontmatter.get("created", frontmatter.get("date", ""))
+ created = normalize_date(str(raw_created), fallback=ctime_iso)
+
+ # Truncate long bodies
+ if len(body) > 50000:
+ body = body[:50000] + "..."
+
+ nid = file_id(full_path, vault_path)
+ title_to_id[title.lower()] = nid
+
+ emit({
+ "upsert": "note",
+ "external_id": nid,
+ "fields": {
+ "title": title,
+ "body": body,
+ "path": rel_path,
+ "tags": tags,
+ "created": created,
+ "modified": modified,
+ "word_count": word_count,
+ }
+ })
+ count += 1
+
+ # Collect wikilinks for second pass
+ wikilinks = extract_wikilinks(body)
+ if wikilinks:
+ note_links[nid] = wikilinks
+
+ if mtime > max_mtime:
+ max_mtime = mtime
+
+ # Second pass: emit links between notes
+ link_count = 0
+ for source_id, targets in note_links.items():
+ for target_name in targets:
+ target_id = title_to_id.get(target_name.lower())
+ if target_id and target_id != source_id:
+ emit({
+ "link": "note",
+ "id": source_id,
+ "to": "note",
+ "to_id": target_id,
+ })
+ link_count += 1
+
+ # Emit cursor
+ if max_mtime > 0:
+ emit({"cursor": str(max_mtime)})
+
+ log("info", f"Synced {count} notes, {link_count} links")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/opencode/adapter.toml b/adapters/opencode/adapter.toml
new file mode 100644
index 000000000000..aa60eacbf88f
--- /dev/null
+++ b/adapters/opencode/adapter.toml
@@ -0,0 +1,82 @@
+[adapter]
+id = "opencode"
+name = "OpenCode"
+description = "Index coding session transcripts from OpenCode (Claude Code). Captures session context, conversation history, tool usage, and code changes."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "terminal"
+min_spacedrive = "0.1.0"
+trust_tier = "authored"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 300
+schedule = "*/30 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "db_path"
+name = "OpenCode Database Path"
+description = "Path to the OpenCode SQLite database. Default: ~/.local/share/opencode/opencode.db"
+type = "string"
+required = false
+default = "~/.local/share/opencode/opencode.db"
+
+[[adapter.config]]
+key = "include_tool_calls"
+name = "Include Tool Calls"
+description = "Include tool call details (file reads, edits, bash commands) in message bodies. Makes sessions more searchable but increases index size."
+type = "boolean"
+required = false
+default = false
+
+[[adapter.config]]
+key = "project_filter"
+name = "Project Filter"
+description = "Only sync sessions from this project directory (e.g. /Users/jamie/Projects/spacebot). Leave empty for all projects."
+type = "string"
+required = false
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "coding-session"
+name = "Coding Sessions"
+icon = "terminal"
+
+[models.session]
+fields.title = "string"
+fields.summary = "string"
+fields.directory = "string"
+fields.project = "string"
+fields.model = "string"
+fields.message_count = "integer"
+fields.total_input_tokens = "integer"
+fields.total_output_tokens = "integer"
+fields.total_cost = "float"
+fields.files_changed = "integer"
+fields.lines_added = "integer"
+fields.lines_deleted = "integer"
+fields.started_at = "datetime"
+fields.ended_at = "datetime"
+has_many = ["message"]
+
+[models.message]
+fields.role = "string"
+fields.body = "string"
+fields.model = "string"
+fields.input_tokens = "integer"
+fields.output_tokens = "integer"
+fields.cost = "float"
+fields.tool_calls = "string"
+fields.timestamp = "datetime"
+belongs_to = ["session"]
+
+[search]
+primary_model = "session"
+title = "title"
+preview = "directory"
+subtitle = "project"
+search_fields = ["title", "summary", "directory", "project"]
+date_field = "started_at"
diff --git a/adapters/opencode/icon.svg b/adapters/opencode/icon.svg
new file mode 100644
index 000000000000..2f04c5d08459
--- /dev/null
+++ b/adapters/opencode/icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/adapters/opencode/sync.py b/adapters/opencode/sync.py
new file mode 100644
index 000000000000..1aff6ea01f8d
--- /dev/null
+++ b/adapters/opencode/sync.py
@@ -0,0 +1,353 @@
+#!/usr/bin/env python3
+"""
+OpenCode adapter for Spacedrive.
+
+Indexes coding session transcripts from OpenCode.
+Reads the opencode.db SQLite database and extracts sessions with their
+conversation messages, aggregating token usage, cost, and tool call metadata.
+
+The DB is copied to a temp file first since OpenCode holds an exclusive lock
+via redb (though the SQLite portion may be readable, we copy for safety).
+
+Supports incremental sync via time_updated cursor.
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def ms_to_iso(timestamp_ms: int) -> str:
+ """Convert millisecond Unix timestamp to ISO 8601."""
+ try:
+ if not timestamp_ms or timestamp_ms == 0:
+ return ""
+ dt = datetime.fromtimestamp(timestamp_ms / 1000, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError):
+ return ""
+
+
+def extract_text_parts(parts_data: list[dict]) -> str:
+ """Extract and concatenate text content from message parts."""
+ texts = []
+ for part in parts_data:
+ data = part.get("data", {})
+ ptype = data.get("type", "")
+ if ptype == "text":
+ text = data.get("text", "").strip()
+ if text:
+ texts.append(text)
+ return "\n\n".join(texts)
+
+
+def extract_tool_summary(parts_data: list[dict]) -> str:
+ """Extract a compact summary of tool calls from message parts."""
+ tools = []
+ for part in parts_data:
+ data = part.get("data", {})
+ if data.get("type") == "tool":
+ tool_name = data.get("tool", "")
+ # Extract a brief description of what the tool did
+ tool_input = data.get("input", {})
+ if tool_name == "read":
+ path = tool_input.get("filePath", tool_input.get("path", ""))
+ if path:
+ tools.append(f"read:{os.path.basename(path)}")
+ elif tool_name == "edit":
+ path = tool_input.get("filePath", tool_input.get("path", ""))
+ if path:
+ tools.append(f"edit:{os.path.basename(path)}")
+ elif tool_name == "write":
+ path = tool_input.get("filePath", tool_input.get("path", ""))
+ if path:
+ tools.append(f"write:{os.path.basename(path)}")
+ elif tool_name == "bash":
+ cmd = tool_input.get("command", "")[:80]
+ if cmd:
+ tools.append(f"bash:{cmd}")
+ elif tool_name == "glob":
+ pattern = tool_input.get("pattern", "")
+ if pattern:
+ tools.append(f"glob:{pattern}")
+ elif tool_name == "grep":
+ pattern = tool_input.get("pattern", "")
+ if pattern:
+ tools.append(f"grep:{pattern}")
+ elif tool_name:
+ tools.append(tool_name)
+ return "; ".join(tools[:50]) # Cap at 50 tool calls
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ db_path = config.get("db_path", "~/.local/share/opencode/opencode.db")
+ db_path = os.path.expanduser(db_path)
+ include_tool_calls = config.get("include_tool_calls", False)
+ project_filter = config.get("project_filter", "")
+
+ if not os.path.exists(db_path):
+ log("error", f"OpenCode database not found: {db_path}")
+ sys.exit(2)
+
+ # Copy the database to avoid lock conflicts
+ tmp_db = tempfile.mktemp(suffix=".db")
+ try:
+ shutil.copy2(db_path, tmp_db)
+ for ext in ["-wal", "-shm"]:
+ src = db_path + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", f"Permission denied reading: {db_path}")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # ── Fetch sessions ──────────────────────────────────────────────
+ conditions = ["s.title != ''"]
+ params = []
+
+ if cursor:
+ conditions.append("s.time_updated > ?")
+ params.append(int(cursor))
+
+ if project_filter:
+ conditions.append("s.directory LIKE ?")
+ params.append(f"%{project_filter}%")
+
+ where = " AND ".join(conditions)
+ sessions = conn.execute(f"""
+ SELECT s.id, s.title, s.directory, s.parent_id,
+ s.summary_files, s.summary_additions, s.summary_deletions,
+ s.time_created, s.time_updated,
+ p.name as project_name, p.worktree as project_worktree
+ FROM session s
+ LEFT JOIN project p ON s.project_id = p.id
+ WHERE {where}
+ ORDER BY s.time_updated ASC
+ """, params).fetchall()
+
+ max_updated = int(cursor) if cursor else 0
+ session_count = 0
+ message_count = 0
+
+ for session in sessions:
+ sid = session["id"]
+ time_updated = session["time_updated"] or 0
+
+ # ── Fetch messages for this session ─────────────────────────
+ messages = conn.execute("""
+ SELECT m.id, m.data, m.time_created, m.time_updated
+ FROM message m
+ WHERE m.session_id = ?
+ ORDER BY m.time_created ASC
+ """, (sid,)).fetchall()
+
+ if not messages:
+ continue
+
+ # ── Fetch parts for all messages in this session ────────────
+ parts_by_message = {}
+ parts = conn.execute("""
+ SELECT p.id, p.message_id, p.data, p.time_created
+ FROM part p
+ WHERE p.session_id = ?
+ ORDER BY p.time_created ASC
+ """, (sid,)).fetchall()
+
+ for part in parts:
+ mid = part["message_id"]
+ try:
+ part_data = json.loads(part["data"])
+ except json.JSONDecodeError:
+ continue
+ if mid not in parts_by_message:
+ parts_by_message[mid] = []
+ parts_by_message[mid].append({"data": part_data})
+
+ # ── Aggregate session stats ─────────────────────────────────
+ total_input = 0
+ total_output = 0
+ total_cost = 0.0
+ models_used = set()
+ session_ended = session["time_created"]
+ summary_parts = []
+ summary_budget = 4000 # chars for FTS summary
+
+ for msg in messages:
+ try:
+ msg_data = json.loads(msg["data"])
+ except json.JSONDecodeError:
+ continue
+
+ tokens = msg_data.get("tokens", {})
+ total_input += (tokens.get("input", 0) or 0)
+ total_output += (tokens.get("output", 0) or 0)
+ cache = tokens.get("cache", {})
+ total_input += (cache.get("read", 0) or 0)
+ total_input += (cache.get("write", 0) or 0)
+ total_cost += (msg_data.get("cost", 0) or 0)
+
+ model = msg_data.get("modelID", "")
+ if model:
+ models_used.add(model)
+
+ msg_time = msg["time_created"] or msg["time_updated"] or 0
+ if msg_time > session_ended:
+ session_ended = msg_time
+
+ # Build summary from conversation text
+ if summary_budget > 0:
+ msg_parts = parts_by_message.get(msg["id"], [])
+ text = extract_text_parts(msg_parts)
+ if text:
+ role = msg_data.get("role", "")
+ # Take first N chars of each message
+ chunk = text[:min(800, summary_budget)]
+ summary_parts.append(chunk)
+ summary_budget -= len(chunk)
+
+ session_summary = "\n".join(summary_parts)
+
+ # Determine primary model (most common or first seen)
+ primary_model = next(iter(models_used), "")
+ # Clean model name — strip provider prefix
+ if "/" in primary_model:
+ primary_model = primary_model.split("/", 1)[1]
+
+ project_name = session["project_name"] or ""
+ if not project_name:
+ # Derive from directory
+ directory = session["directory"] or ""
+ if directory:
+ project_name = os.path.basename(directory.rstrip("/"))
+
+ # ── Emit session ────────────────────────────────────────────
+ emit({
+ "upsert": "session",
+ "external_id": sid,
+ "fields": {
+ "title": (session["title"] or "Untitled session")[:500],
+ "summary": session_summary,
+ "directory": session["directory"] or "",
+ "project": project_name,
+ "model": primary_model,
+ "message_count": len(messages),
+ "total_input_tokens": total_input,
+ "total_output_tokens": total_output,
+ "total_cost": round(total_cost, 6),
+ "files_changed": session["summary_files"] or 0,
+ "lines_added": session["summary_additions"] or 0,
+ "lines_deleted": session["summary_deletions"] or 0,
+ "started_at": ms_to_iso(session["time_created"]),
+ "ended_at": ms_to_iso(session_ended),
+ }
+ })
+ session_count += 1
+
+ # ── Emit messages ───────────────────────────────────────────
+ for msg in messages:
+ try:
+ msg_data = json.loads(msg["data"])
+ except json.JSONDecodeError:
+ continue
+
+ role = msg_data.get("role", "unknown")
+ msg_parts = parts_by_message.get(msg["id"], [])
+
+ # Build message body from text parts
+ body = extract_text_parts(msg_parts)
+
+ # Optionally include tool call summaries
+ tool_summary = ""
+ if include_tool_calls:
+ tool_summary = extract_tool_summary(msg_parts)
+ if tool_summary and body:
+ body = body + "\n\n[Tools: " + tool_summary + "]"
+ elif tool_summary:
+ body = "[Tools: " + tool_summary + "]"
+
+ # Skip empty messages (e.g. step-start/step-finish only)
+ if not body:
+ continue
+
+ tokens = msg_data.get("tokens", {})
+ input_tokens = (tokens.get("input", 0) or 0)
+ cache = tokens.get("cache", {})
+ input_tokens += (cache.get("read", 0) or 0) + (cache.get("write", 0) or 0)
+ output_tokens = (tokens.get("output", 0) or 0)
+ cost = msg_data.get("cost", 0) or 0
+
+ model = msg_data.get("modelID", "")
+ if "/" in model:
+ model = model.split("/", 1)[1]
+
+ msg_time = msg["time_created"] or msg["time_updated"]
+
+ emit({
+ "upsert": "message",
+ "external_id": msg["id"],
+ "fields": {
+ "role": role,
+ "body": body[:50000], # Cap at 50KB per message
+ "model": model,
+ "input_tokens": input_tokens,
+ "output_tokens": output_tokens,
+ "cost": round(cost, 6),
+ "tool_calls": tool_summary[:5000] if tool_summary else "",
+ "timestamp": ms_to_iso(msg_time),
+ },
+ "relations": {
+ "session": sid
+ }
+ })
+ message_count += 1
+
+ if time_updated > max_updated:
+ max_updated = time_updated
+
+ # Emit cursor
+ if max_updated > 0:
+ emit({"cursor": str(max_updated)})
+
+ log("info", f"Synced {session_count} sessions, {message_count} messages")
+
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/safari-history/adapter.toml b/adapters/safari-history/adapter.toml
new file mode 100644
index 000000000000..77afcefe4d49
--- /dev/null
+++ b/adapters/safari-history/adapter.toml
@@ -0,0 +1,53 @@
+[adapter]
+id = "safari-history"
+name = "Safari History"
+description = "Index browsing history from Safari on macOS. Requires Full Disk Access for the running process."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "globe"
+min_spacedrive = "0.1.0"
+trust_tier = "external"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 120
+schedule = "*/15 * * * *"
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "max_results"
+name = "Max Results"
+description = "Maximum URLs to index per sync"
+type = "integer"
+required = false
+default = 10000
+
+[[adapter.config]]
+key = "min_visit_count"
+name = "Minimum Visit Count"
+description = "Only index URLs visited at least this many times"
+type = "integer"
+required = false
+default = 1
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "history"
+name = "Browser History"
+icon = "globe"
+
+[models.page]
+fields.title = "string"
+fields.url = "string"
+fields.visit_count = "integer"
+fields.last_visit = "datetime"
+
+[search]
+primary_model = "page"
+title = "title"
+preview = "url"
+subtitle = "last_visit"
+search_fields = ["title", "url"]
+date_field = "last_visit"
diff --git a/adapters/safari-history/icon.svg b/adapters/safari-history/icon.svg
new file mode 100644
index 000000000000..b27b36417280
--- /dev/null
+++ b/adapters/safari-history/icon.svg
@@ -0,0 +1,13 @@
+
diff --git a/adapters/safari-history/sync.py b/adapters/safari-history/sync.py
new file mode 100644
index 000000000000..588f115e489b
--- /dev/null
+++ b/adapters/safari-history/sync.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python3
+"""
+Safari History adapter for Spacedrive.
+
+Reads the Safari History.db SQLite database on macOS.
+Copies the DB to a temp file first since Safari holds a lock on it.
+
+Requires Full Disk Access to read ~/Library/Safari/History.db.
+
+Supports incremental sync via visit_time cursor.
+"""
+
+import json
+import sys
+import os
+import sqlite3
+import shutil
+import tempfile
+from datetime import datetime, timezone
+
+# Safari stores timestamps as seconds since 2001-01-01 (Core Data / Cocoa epoch)
+CORE_DATA_EPOCH = 978307200
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def safari_time_to_iso(timestamp: float) -> str:
+ """Convert Safari/Core Data timestamp (seconds since 2001-01-01) to ISO 8601 UTC."""
+ try:
+ if not timestamp or timestamp == 0:
+ return ""
+ unix_seconds = float(timestamp) + CORE_DATA_EPOCH
+ if unix_seconds < 0:
+ return ""
+ dt = datetime.fromtimestamp(unix_seconds, tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError, TypeError):
+ return ""
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+ cursor = input_data.get("cursor")
+
+ # Safari History.db location
+ history_path = os.path.expanduser("~/Library/Safari/History.db")
+ if not os.path.exists(history_path):
+ log("error", f"Safari history database not found: {history_path}")
+ sys.exit(2)
+
+ min_visit_count = int(config.get("min_visit_count", 1))
+ max_results = int(config.get("max_results", 10000))
+
+ # Copy the database since Safari holds a lock
+ tmp_db = tempfile.mktemp(suffix=".db")
+ try:
+ shutil.copy2(history_path, tmp_db)
+ for ext in ["-wal", "-shm"]:
+ src = history_path + ext
+ if os.path.exists(src):
+ shutil.copy2(src, tmp_db + ext)
+ except PermissionError:
+ log("error", f"Permission denied reading: {history_path}. Grant Full Disk Access to the running process.")
+ sys.exit(2)
+ except Exception as e:
+ log("error", f"Failed to copy Safari history database: {e}")
+ sys.exit(2)
+
+ try:
+ conn = sqlite3.connect(tmp_db)
+ conn.row_factory = sqlite3.Row
+
+ # Safari schema:
+ # history_items: id, url, domain_expansion, visit_count, daily_visit_counts, ...
+ # history_visits: id, history_item, visit_time, title, ...
+ #
+ # We join to get the most recent visit per URL with its title.
+
+ conditions = ["hi.visit_count >= ?"]
+ params = [min_visit_count]
+
+ if cursor:
+ conditions.append("hv.visit_time > ?")
+ params.append(float(cursor))
+
+ where = " AND ".join(conditions)
+
+ query = f"""
+ SELECT
+ hi.id AS item_id,
+ hi.url,
+ hi.visit_count,
+ hv.visit_time,
+ hv.title
+ FROM history_items hi
+ JOIN history_visits hv ON hv.history_item = hi.id
+ WHERE hv.id = (
+ SELECT hv2.id FROM history_visits hv2
+ WHERE hv2.history_item = hi.id
+ ORDER BY hv2.visit_time DESC LIMIT 1
+ )
+ AND {where}
+ ORDER BY hv.visit_time DESC
+ LIMIT ?
+ """
+ params.append(max_results)
+
+ rows = conn.execute(query, params).fetchall()
+
+ max_visit_time = float(cursor) if cursor else 0
+ count = 0
+
+ for row in rows:
+ item_id = str(row["item_id"])
+ url = row["url"] or ""
+ title = row["title"] or url
+ visit_count = row["visit_count"] or 0
+ visit_time = row["visit_time"] or 0
+
+ # Skip internal pages
+ if url.startswith(("about:", "blob:", "data:")):
+ continue
+
+ last_visit_iso = safari_time_to_iso(visit_time)
+
+ emit({
+ "upsert": "page",
+ "external_id": item_id,
+ "fields": {
+ "title": title[:500],
+ "url": url[:2000],
+ "visit_count": visit_count,
+ "last_visit": last_visit_iso,
+ }
+ })
+ count += 1
+
+ if visit_time > max_visit_time:
+ max_visit_time = visit_time
+
+ # Emit cursor for incremental sync
+ if max_visit_time > 0:
+ emit({"cursor": str(max_visit_time)})
+
+ log("info", f"Synced {count} pages (min visits: {min_visit_count})")
+
+ conn.close()
+
+ except sqlite3.Error as e:
+ log("error", f"SQLite error: {e}")
+ sys.exit(1)
+ finally:
+ for f in [tmp_db, tmp_db + "-wal", tmp_db + "-shm"]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/adapters/slack/adapter.toml b/adapters/slack/adapter.toml
new file mode 100644
index 000000000000..5543e867ca30
--- /dev/null
+++ b/adapters/slack/adapter.toml
@@ -0,0 +1,66 @@
+[adapter]
+id = "slack"
+name = "Slack Export"
+description = "Index messages, channels, and users from a Slack workspace export (JSON). Download your export from Slack admin settings."
+version = "0.1.0"
+author = "spacedrive"
+license = "MIT"
+icon = "message-square"
+min_spacedrive = "0.1.0"
+trust_tier = "collaborative"
+
+[adapter.runtime]
+command = "python3 sync.py"
+timeout = 600
+schedule = ""
+requires = ["python3 >= 3.9"]
+
+[[adapter.config]]
+key = "export_path"
+name = "Export Directory"
+description = "Path to the unzipped Slack export directory (contains channels.json, users.json, and channel folders)"
+type = "string"
+required = true
+
+[[adapter.config]]
+key = "max_messages"
+name = "Max Messages"
+description = "Maximum messages to index (0 = unlimited)"
+type = "integer"
+required = false
+default = 0
+
+# ── Data type schema ────────────────────────────────────────────────────
+
+[data_type]
+id = "slack"
+name = "Slack Messages"
+icon = "message-square"
+
+[models.channel]
+fields.name = "string"
+fields.purpose = "text"
+fields.topic = "string"
+fields.is_archived = "boolean"
+fields.member_count = "integer"
+has_many = ["message"]
+
+[models.message]
+fields.text = "text"
+fields.author = "string"
+fields.timestamp = "datetime"
+fields.thread_ts = "string"
+fields.reply_count = "integer"
+fields.reactions = "string"
+
+[models.message.relations]
+belongs_to = ["channel"]
+self_referential = "parent_id"
+
+[search]
+primary_model = "message"
+title = "author"
+preview = "text"
+subtitle = "timestamp"
+search_fields = ["text", "author"]
+date_field = "timestamp"
diff --git a/adapters/slack/icon.svg b/adapters/slack/icon.svg
new file mode 100644
index 000000000000..98dc80b265d6
--- /dev/null
+++ b/adapters/slack/icon.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/adapters/slack/sync.py b/adapters/slack/sync.py
new file mode 100644
index 000000000000..6d3d491843e1
--- /dev/null
+++ b/adapters/slack/sync.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+"""
+Slack Export adapter for Spacedrive.
+
+Reads a Slack workspace export (the unzipped JSON directory structure).
+Export format: channels.json, users.json, and one folder per channel
+containing daily JSON files (YYYY-MM-DD.json).
+
+This is a full-scan adapter — no incremental cursor since exports are
+point-in-time snapshots. Re-exporting and re-syncing will upsert all data.
+"""
+
+import json
+import sys
+import os
+import glob
+from datetime import datetime, timezone
+
+
+def log(level: str, message: str):
+ print(json.dumps({"log": level, "message": message}), flush=True)
+
+
+def emit(operation: dict):
+ print(json.dumps(operation), flush=True)
+
+
+def ts_to_iso(ts: str) -> str:
+ """Convert Slack timestamp (Unix float as string, e.g. '1706123456.789012') to ISO 8601 UTC."""
+ try:
+ if not ts:
+ return ""
+ dt = datetime.fromtimestamp(float(ts), tz=timezone.utc)
+ return dt.isoformat()
+ except (ValueError, OSError, TypeError):
+ return ""
+
+
+def load_json(path: str):
+ """Load a JSON file, returning None on failure."""
+ try:
+ with open(path, "r", encoding="utf-8") as f:
+ return json.load(f)
+ except (json.JSONDecodeError, OSError) as e:
+ log("warn", f"Failed to read {path}: {e}")
+ return None
+
+
+def main():
+ try:
+ input_data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ log("error", f"Invalid input JSON: {e}")
+ sys.exit(2)
+
+ config = input_data.get("config", {})
+
+ export_path = config.get("export_path", "")
+ if not export_path:
+ log("error", "Missing required config: export_path")
+ sys.exit(2)
+
+ export_path = os.path.expanduser(export_path)
+ if not os.path.isdir(export_path):
+ log("error", f"Export directory not found: {export_path}")
+ sys.exit(2)
+
+ max_messages = int(config.get("max_messages", 0))
+
+ # ── Load users for display name lookup ───────────────────────────────
+ users_map = {}
+ users_file = os.path.join(export_path, "users.json")
+ users_data = load_json(users_file)
+ if users_data:
+ for user in users_data:
+ uid = user.get("id", "")
+ profile = user.get("profile", {})
+ display = (
+ profile.get("display_name")
+ or profile.get("real_name")
+ or user.get("real_name")
+ or user.get("name", uid)
+ )
+ users_map[uid] = display
+ else:
+ log("warn", "No users.json found, author names will be user IDs")
+
+ # ── Load channels ────────────────────────────────────────────────────
+ channels = []
+ for filename in ["channels.json", "groups.json", "mpims.json", "dms.json"]:
+ channels_file = os.path.join(export_path, filename)
+ data = load_json(channels_file)
+ if data:
+ channels.extend(data)
+
+ if not channels:
+ log("error", "No channel data found in export (expected channels.json)")
+ sys.exit(2)
+
+ channel_name_map = {}
+ for ch in channels:
+ ch_id = ch.get("id", "")
+ ch_name = ch.get("name", ch_id)
+ purpose = ch.get("purpose", {})
+ purpose_text = purpose.get("value", "") if isinstance(purpose, dict) else str(purpose)
+ topic = ch.get("topic", {})
+ topic_text = topic.get("value", "") if isinstance(topic, dict) else str(topic)
+
+ channel_name_map[ch_id] = ch_name
+ # Also map folder name -> channel id (folders are named by channel name)
+ channel_name_map[ch_name] = ch_id
+
+ members = ch.get("members", [])
+
+ emit({
+ "upsert": "channel",
+ "external_id": ch_id,
+ "fields": {
+ "name": ch_name,
+ "purpose": purpose_text[:5000],
+ "topic": topic_text[:500],
+ "is_archived": ch.get("is_archived", False),
+ "member_count": len(members) if isinstance(members, list) else 0,
+ }
+ })
+
+ log("info", f"Loaded {len(channels)} channels")
+
+ # ── Load messages from channel folders ───────────────────────────────
+ msg_count = 0
+ thread_parents = {} # ts -> external_id, for parent linking
+
+ # Iterate channel directories
+ for entry in sorted(os.listdir(export_path)):
+ channel_dir = os.path.join(export_path, entry)
+ if not os.path.isdir(channel_dir) or entry.startswith("."):
+ continue
+
+ # Find the channel ID for this folder
+ ch_id = channel_name_map.get(entry)
+ if not ch_id:
+ # Folder name might be the channel ID itself
+ ch_id = entry
+
+ # Read all daily JSON files sorted chronologically
+ day_files = sorted(glob.glob(os.path.join(channel_dir, "*.json")))
+
+ for day_file in day_files:
+ messages = load_json(day_file)
+ if not messages or not isinstance(messages, list):
+ continue
+
+ for msg in messages:
+ if max_messages > 0 and msg_count >= max_messages:
+ break
+
+ ts = msg.get("ts", "")
+ if not ts:
+ continue
+
+ # Skip subtypes that aren't real messages
+ subtype = msg.get("subtype", "")
+ if subtype in ("channel_join", "channel_leave", "channel_topic",
+ "channel_purpose", "channel_name", "channel_archive",
+ "channel_unarchive", "pinned_item", "unpinned_item"):
+ continue
+
+ user_id = msg.get("user", msg.get("bot_id", "unknown"))
+ author = users_map.get(user_id, user_id)
+ text = msg.get("text", "")
+
+ # Expand user mentions: <@U1234> -> @display_name
+ for uid, uname in users_map.items():
+ text = text.replace(f"<@{uid}>", f"@{uname}")
+
+ timestamp_iso = ts_to_iso(ts)
+ thread_ts = msg.get("thread_ts", "")
+ reply_count = msg.get("reply_count", 0)
+
+ # Build reactions string
+ reactions = msg.get("reactions", [])
+ reactions_str = ""
+ if reactions:
+ parts = []
+ for r in reactions:
+ name = r.get("name", "")
+ count = r.get("count", 0)
+ if name:
+ parts.append(f":{name}: {count}")
+ reactions_str = ", ".join(parts)
+
+ msg_id = f"{ch_id}_{ts}"
+
+ emit({
+ "upsert": "message",
+ "external_id": msg_id,
+ "fields": {
+ "text": text[:50000],
+ "author": author[:200],
+ "timestamp": timestamp_iso,
+ "thread_ts": thread_ts,
+ "reply_count": reply_count,
+ "reactions": reactions_str[:1000],
+ "channel_id": ch_id,
+ }
+ })
+
+ # Track thread parents for linking
+ if not thread_ts or thread_ts == ts:
+ thread_parents[ts] = msg_id
+
+ msg_count += 1
+
+ if max_messages > 0 and msg_count >= max_messages:
+ break
+
+ if max_messages > 0 and msg_count >= max_messages:
+ break
+
+ log("info", f"Synced {msg_count} messages across {len(channels)} channels")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/apps/api b/apps/api
deleted file mode 160000
index ae553a17ebb8..000000000000
--- a/apps/api
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ae553a17ebb8e88b515d49f6be60c1de4f4b79ee
diff --git a/apps/ios b/apps/ios
deleted file mode 160000
index c1e5988d0ea4..000000000000
--- a/apps/ios
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c1e5988d0ea40567ad2c91aacfe8d357589d6704
diff --git a/apps/landing b/apps/landing
deleted file mode 160000
index 72e6628e1a8e..000000000000
--- a/apps/landing
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 72e6628e1a8e650d63f1bce4f7c0f444306b5bd2
diff --git a/apps/macos b/apps/macos
deleted file mode 160000
index 5127f0fd2dc7..000000000000
--- a/apps/macos
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5127f0fd2dc7ec0fff3c91cd02022e05662fa469
diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock
index 5b100d9d4285..bfcaf1eaabc4 100644
--- a/apps/mobile/ios/Podfile.lock
+++ b/apps/mobile/ios/Podfile.lock
@@ -261,8 +261,8 @@ PODS:
- hermes-engine (0.81.5):
- hermes-engine/Pre-built (= 0.81.5)
- hermes-engine/Pre-built (0.81.5)
- - libavif/core (0.11.1)
- - libavif/libdav1d (0.11.1):
+ - libavif/core (1.0.0)
+ - libavif/libdav1d (1.0.0):
- libavif/core
- libdav1d (>= 0.6.0)
- libdav1d (1.2.0)
@@ -2348,9 +2348,9 @@ PODS:
- Yoga
- SDMobileCore (1.0.0):
- ExpoModulesCore
- - SDWebImage (5.21.5):
- - SDWebImage/Core (= 5.21.5)
- - SDWebImage/Core (5.21.5)
+ - SDWebImage (5.21.7):
+ - SDWebImage/Core (= 5.21.7)
+ - SDWebImage/Core (5.21.7)
- SDWebImageAVIFCoder (0.11.1):
- libavif/core (>= 0.11.0)
- SDWebImage (~> 5.10)
@@ -2367,28 +2367,28 @@ PODS:
- ZXingObjC/Core
DEPENDENCIES:
- - "EXConstants (from `../../../node_modules/.bun/expo-constants@18.0.11+668c6eeaed077a0c/node_modules/expo-constants/ios`)"
+ - "EXConstants (from `../../../node_modules/.bun/expo-constants@18.0.11+3ee4ad13fefe912b/node_modules/expo-constants/ios`)"
- "EXJSONUtils (from `../../../node_modules/.bun/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios`)"
- - "EXManifests (from `../../../node_modules/.bun/expo-manifests@1.0.10+668c6eeaed077a0c/node_modules/expo-manifests/ios`)"
- - "Expo (from `../../../node_modules/.bun/expo@54.0.27+668c6eeaed077a0c/node_modules/expo`)"
- - "expo-dev-client (from `../../../node_modules/.bun/expo-dev-client@6.0.20+668c6eeaed077a0c/node_modules/expo-dev-client/ios`)"
- - "expo-dev-launcher (from `../../../node_modules/.bun/expo-dev-launcher@6.0.20+668c6eeaed077a0c/node_modules/expo-dev-launcher`)"
- - "expo-dev-menu (from `../../../node_modules/.bun/expo-dev-menu@7.0.18+668c6eeaed077a0c/node_modules/expo-dev-menu`)"
- - "expo-dev-menu-interface (from `../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+668c6eeaed077a0c/node_modules/expo-dev-menu-interface/ios`)"
- - "ExpoAsset (from `../../../node_modules/.bun/expo-asset@12.0.11+668c6eeaed077a0c/node_modules/expo-asset/ios`)"
- - "ExpoBlur (from `../../../node_modules/.bun/expo-blur@15.0.8+668c6eeaed077a0c/node_modules/expo-blur/ios`)"
- - "ExpoCamera (from `../../../node_modules/.bun/expo-camera@17.0.10+668c6eeaed077a0c/node_modules/expo-camera/ios`)"
- - "ExpoDocumentPicker (from `../../../node_modules/.bun/expo-document-picker@14.0.8+668c6eeaed077a0c/node_modules/expo-document-picker/ios`)"
- - "ExpoFileSystem (from `../../../node_modules/.bun/expo-file-system@19.0.20+668c6eeaed077a0c/node_modules/expo-file-system/ios`)"
+ - "EXManifests (from `../../../node_modules/.bun/expo-manifests@1.0.10+3ee4ad13fefe912b/node_modules/expo-manifests/ios`)"
+ - "Expo (from `../../../node_modules/.bun/expo@54.0.27+3ee4ad13fefe912b/node_modules/expo`)"
+ - "expo-dev-client (from `../../../node_modules/.bun/expo-dev-client@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-client/ios`)"
+ - "expo-dev-launcher (from `../../../node_modules/.bun/expo-dev-launcher@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-launcher`)"
+ - "expo-dev-menu (from `../../../node_modules/.bun/expo-dev-menu@7.0.18+3ee4ad13fefe912b/node_modules/expo-dev-menu`)"
+ - "expo-dev-menu-interface (from `../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-dev-menu-interface/ios`)"
+ - "ExpoAsset (from `../../../node_modules/.bun/expo-asset@12.0.11+3ee4ad13fefe912b/node_modules/expo-asset/ios`)"
+ - "ExpoBlur (from `../../../node_modules/.bun/expo-blur@15.0.8+3ee4ad13fefe912b/node_modules/expo-blur/ios`)"
+ - "ExpoCamera (from `../../../node_modules/.bun/expo-camera@17.0.10+3ee4ad13fefe912b/node_modules/expo-camera/ios`)"
+ - "ExpoDocumentPicker (from `../../../node_modules/.bun/expo-document-picker@14.0.8+3ee4ad13fefe912b/node_modules/expo-document-picker/ios`)"
+ - "ExpoFileSystem (from `../../../node_modules/.bun/expo-file-system@19.0.20+3ee4ad13fefe912b/node_modules/expo-file-system/ios`)"
- "ExpoFont (from `../../../node_modules/.bun/expo-font@14.0.10+c262bee79918334c/node_modules/expo-font/ios`)"
- - "ExpoHaptics (from `../../../node_modules/.bun/expo-haptics@15.0.8+668c6eeaed077a0c/node_modules/expo-haptics/ios`)"
- - "ExpoHead (from `../../../node_modules/.bun/expo-router@6.0.17+a55fb14e5eb2d958/node_modules/expo-router/ios`)"
- - "ExpoImage (from `../../../node_modules/.bun/expo-image@3.0.11+668c6eeaed077a0c/node_modules/expo-image/ios`)"
+ - "ExpoHaptics (from `../../../node_modules/.bun/expo-haptics@15.0.8+3ee4ad13fefe912b/node_modules/expo-haptics/ios`)"
+ - "ExpoHead (from `../../../node_modules/.bun/expo-router@6.0.17+7dc14032edcce378/node_modules/expo-router/ios`)"
+ - "ExpoImage (from `../../../node_modules/.bun/expo-image@3.0.11+3ee4ad13fefe912b/node_modules/expo-image/ios`)"
- "ExpoKeepAwake (from `../../../node_modules/.bun/expo-keep-awake@15.0.8+ddb0696906414ead/node_modules/expo-keep-awake/ios`)"
- - "ExpoLinking (from `../../../node_modules/.bun/expo-linking@8.0.10+668c6eeaed077a0c/node_modules/expo-linking/ios`)"
+ - "ExpoLinking (from `../../../node_modules/.bun/expo-linking@8.0.10+3ee4ad13fefe912b/node_modules/expo-linking/ios`)"
- "ExpoModulesCore (from `../../../node_modules/.bun/expo-modules-core@3.0.28+87dd5a4c738f4c73/node_modules/expo-modules-core`)"
- - "ExpoSplashScreen (from `../../../node_modules/.bun/expo-splash-screen@31.0.12+668c6eeaed077a0c/node_modules/expo-splash-screen/ios`)"
- - "EXUpdatesInterface (from `../../../node_modules/.bun/expo-updates-interface@2.0.0+668c6eeaed077a0c/node_modules/expo-updates-interface/ios`)"
+ - "ExpoSplashScreen (from `../../../node_modules/.bun/expo-splash-screen@31.0.12+3ee4ad13fefe912b/node_modules/expo-splash-screen/ios`)"
+ - "EXUpdatesInterface (from `../../../node_modules/.bun/expo-updates-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-updates-interface/ios`)"
- "FBLazyVector (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/FBLazyVector`)"
- "hermes-engine (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)"
- "LiquidGlass (from `../../../node_modules/.bun/@callstack+liquid-glass@0.7.0+87dd5a4c738f4c73/node_modules/@callstack/liquid-glass`)"
@@ -2482,49 +2482,49 @@ SPEC REPOS:
EXTERNAL SOURCES:
EXConstants:
- :path: "../../../node_modules/.bun/expo-constants@18.0.11+668c6eeaed077a0c/node_modules/expo-constants/ios"
+ :path: "../../../node_modules/.bun/expo-constants@18.0.11+3ee4ad13fefe912b/node_modules/expo-constants/ios"
EXJSONUtils:
:path: "../../../node_modules/.bun/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios"
EXManifests:
- :path: "../../../node_modules/.bun/expo-manifests@1.0.10+668c6eeaed077a0c/node_modules/expo-manifests/ios"
+ :path: "../../../node_modules/.bun/expo-manifests@1.0.10+3ee4ad13fefe912b/node_modules/expo-manifests/ios"
Expo:
- :path: "../../../node_modules/.bun/expo@54.0.27+668c6eeaed077a0c/node_modules/expo"
+ :path: "../../../node_modules/.bun/expo@54.0.27+3ee4ad13fefe912b/node_modules/expo"
expo-dev-client:
- :path: "../../../node_modules/.bun/expo-dev-client@6.0.20+668c6eeaed077a0c/node_modules/expo-dev-client/ios"
+ :path: "../../../node_modules/.bun/expo-dev-client@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-client/ios"
expo-dev-launcher:
- :path: "../../../node_modules/.bun/expo-dev-launcher@6.0.20+668c6eeaed077a0c/node_modules/expo-dev-launcher"
+ :path: "../../../node_modules/.bun/expo-dev-launcher@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-launcher"
expo-dev-menu:
- :path: "../../../node_modules/.bun/expo-dev-menu@7.0.18+668c6eeaed077a0c/node_modules/expo-dev-menu"
+ :path: "../../../node_modules/.bun/expo-dev-menu@7.0.18+3ee4ad13fefe912b/node_modules/expo-dev-menu"
expo-dev-menu-interface:
- :path: "../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+668c6eeaed077a0c/node_modules/expo-dev-menu-interface/ios"
+ :path: "../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-dev-menu-interface/ios"
ExpoAsset:
- :path: "../../../node_modules/.bun/expo-asset@12.0.11+668c6eeaed077a0c/node_modules/expo-asset/ios"
+ :path: "../../../node_modules/.bun/expo-asset@12.0.11+3ee4ad13fefe912b/node_modules/expo-asset/ios"
ExpoBlur:
- :path: "../../../node_modules/.bun/expo-blur@15.0.8+668c6eeaed077a0c/node_modules/expo-blur/ios"
+ :path: "../../../node_modules/.bun/expo-blur@15.0.8+3ee4ad13fefe912b/node_modules/expo-blur/ios"
ExpoCamera:
- :path: "../../../node_modules/.bun/expo-camera@17.0.10+668c6eeaed077a0c/node_modules/expo-camera/ios"
+ :path: "../../../node_modules/.bun/expo-camera@17.0.10+3ee4ad13fefe912b/node_modules/expo-camera/ios"
ExpoDocumentPicker:
- :path: "../../../node_modules/.bun/expo-document-picker@14.0.8+668c6eeaed077a0c/node_modules/expo-document-picker/ios"
+ :path: "../../../node_modules/.bun/expo-document-picker@14.0.8+3ee4ad13fefe912b/node_modules/expo-document-picker/ios"
ExpoFileSystem:
- :path: "../../../node_modules/.bun/expo-file-system@19.0.20+668c6eeaed077a0c/node_modules/expo-file-system/ios"
+ :path: "../../../node_modules/.bun/expo-file-system@19.0.20+3ee4ad13fefe912b/node_modules/expo-file-system/ios"
ExpoFont:
:path: "../../../node_modules/.bun/expo-font@14.0.10+c262bee79918334c/node_modules/expo-font/ios"
ExpoHaptics:
- :path: "../../../node_modules/.bun/expo-haptics@15.0.8+668c6eeaed077a0c/node_modules/expo-haptics/ios"
+ :path: "../../../node_modules/.bun/expo-haptics@15.0.8+3ee4ad13fefe912b/node_modules/expo-haptics/ios"
ExpoHead:
- :path: "../../../node_modules/.bun/expo-router@6.0.17+a55fb14e5eb2d958/node_modules/expo-router/ios"
+ :path: "../../../node_modules/.bun/expo-router@6.0.17+7dc14032edcce378/node_modules/expo-router/ios"
ExpoImage:
- :path: "../../../node_modules/.bun/expo-image@3.0.11+668c6eeaed077a0c/node_modules/expo-image/ios"
+ :path: "../../../node_modules/.bun/expo-image@3.0.11+3ee4ad13fefe912b/node_modules/expo-image/ios"
ExpoKeepAwake:
:path: "../../../node_modules/.bun/expo-keep-awake@15.0.8+ddb0696906414ead/node_modules/expo-keep-awake/ios"
ExpoLinking:
- :path: "../../../node_modules/.bun/expo-linking@8.0.10+668c6eeaed077a0c/node_modules/expo-linking/ios"
+ :path: "../../../node_modules/.bun/expo-linking@8.0.10+3ee4ad13fefe912b/node_modules/expo-linking/ios"
ExpoModulesCore:
:path: "../../../node_modules/.bun/expo-modules-core@3.0.28+87dd5a4c738f4c73/node_modules/expo-modules-core"
ExpoSplashScreen:
- :path: "../../../node_modules/.bun/expo-splash-screen@31.0.12+668c6eeaed077a0c/node_modules/expo-splash-screen/ios"
+ :path: "../../../node_modules/.bun/expo-splash-screen@31.0.12+3ee4ad13fefe912b/node_modules/expo-splash-screen/ios"
EXUpdatesInterface:
- :path: "../../../node_modules/.bun/expo-updates-interface@2.0.0+668c6eeaed077a0c/node_modules/expo-updates-interface/ios"
+ :path: "../../../node_modules/.bun/expo-updates-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-updates-interface/ios"
FBLazyVector:
:path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/FBLazyVector"
hermes-engine:
@@ -2708,7 +2708,7 @@ SPEC CHECKSUMS:
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12
hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172
- libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
+ libavif: 5f8e715bea24debec477006f21ef9e95432e254d
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
LiquidGlass: 2bbbcea458d5a2b018c2dd024040c4023d73eec8
@@ -2785,8 +2785,8 @@ SPEC CHECKSUMS:
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
RNWorklets: bdca513296f69bf7fe8418208da31447c65b23ed
- SDMobileCore: 1f342704b37de152ac5664ce73fe71ea881f20c2
- SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
+ SDMobileCore: 1dd1a6e1e9d5e9702dcca9cc51a87bc678b0a6c4
+ SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js
index 76838cf0af3f..c0f41157e035 100644
--- a/apps/mobile/metro.config.js
+++ b/apps/mobile/metro.config.js
@@ -1,18 +1,28 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
+const fs = require("fs");
const path = require("path");
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
+const spaceUiTokensRoot = path.dirname(
+ require.resolve("@spacedrive/tokens/raw-colors", { paths: [projectRoot, workspaceRoot] })
+);
+const spaceUiTokensNodeModules = path.resolve(spaceUiTokensRoot, "node_modules");
+
+const existingPaths = (paths) => [...new Set(paths.filter((filePath) => fs.existsSync(filePath)))];
const config = getDefaultConfig(projectRoot);
-// Watch only relevant directories for hot reload (not entire monorepo)
-// This avoids watching Rust target/ dirs (4.5GB+) and other build artifacts
-config.watchFolders = [
+// Watch only the app sources and hoisted workspace deps Metro needs to resolve.
+// Expo Router can resolve to files in the hoisted Bun node_modules tree.
+config.watchFolders = existingPaths([
path.resolve(projectRoot, "src"),
path.resolve(workspaceRoot, "packages"),
-];
+ path.resolve(workspaceRoot, "node_modules"),
+ spaceUiTokensRoot,
+ spaceUiTokensNodeModules,
+]);
// Configure resolver for monorepo and SVG support
config.resolver = {
@@ -24,10 +34,13 @@ config.resolver = {
// Critical for Bun monorepo - resolve node_modules from local and workspace root
// Local node_modules takes priority to ensure correct React version
- nodeModulesPaths: [
+ nodeModulesPaths: existingPaths([
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
- ],
+ spaceUiTokensNodeModules,
+ ]),
+
+ unstable_enableSymlinks: true,
// Exclude build outputs
blockList: [
@@ -37,6 +50,7 @@ config.resolver = {
// Dynamically resolve React/React Native from wherever the package manager installed them
extraNodeModules: {
+ "@spacedrive/tokens": spaceUiTokensRoot,
react: path.dirname(require.resolve("react/package.json", { paths: [projectRoot, workspaceRoot] })),
"react-native": path.dirname(
require.resolve("react-native/package.json", { paths: [projectRoot, workspaceRoot] })
diff --git a/apps/mobile/modules/sd-mobile-core/core/src/lib.rs b/apps/mobile/modules/sd-mobile-core/core/src/lib.rs
index ee0581abf0fc..3998ac25fb13 100644
--- a/apps/mobile/modules/sd-mobile-core/core/src/lib.rs
+++ b/apps/mobile/modules/sd-mobile-core/core/src/lib.rs
@@ -126,8 +126,7 @@ pub unsafe extern "C" fn initialize_core(
}
// Initialize core
- let core =
- rt.block_on(async { Core::new_with_config(data_path, None, device_name_opt).await });
+ let core = rt.block_on(async { Core::new_with_config(data_path, None, device_name_opt).await });
let mut core = match core {
Ok(core) => core,
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index ac82c168ffe8..de7ada09865e 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -1,77 +1,77 @@
{
- "name": "@sd/mobile",
- "version": "0.1.0",
- "private": true,
- "main": "expo-router/entry",
- "scripts": {
- "start": "bunx expo start",
- "android": "bunx expo run:android",
- "ios": "bunx expo run:ios",
- "patch": "./scripts/patch-svg.sh",
- "prebuild": "bun run patch && bunx expo prebuild",
- "prebuild:clean": "bun run patch && bunx expo prebuild --clean",
- "xcode": "open ios/Spacedrive.xcworkspace",
- "android-studio": "open -a '/Applications/Android Studio.app' ./android",
- "data": "open \"$(xcrun simctl get_app_container booted com.spacedrive.app data)\"",
- "lint": "eslint src --cache",
- "typecheck": "tsc -b"
- },
- "dependencies": {
- "@callstack/liquid-glass": "^0.7.0",
- "@dev-plugins/react-query": "^0.4.0",
- "@gorhom/bottom-sheet": "^5.0.6",
- "@react-native-async-storage/async-storage": "^2.1.0",
- "@react-native-clipboard/clipboard": "^1.16.3",
- "@react-native-community/slider": "^5.1.1",
- "@react-navigation/bottom-tabs": "^7.2.0",
- "@react-navigation/drawer": "^7.1.1",
- "@react-navigation/native": "^7.0.14",
- "@react-navigation/native-stack": "^7.2.0",
- "@sd/assets": "workspace:*",
- "@sd/ts-client": "workspace:*",
- "@sd/ui": "workspace:*",
- "@shopify/flash-list": "2.0.2",
- "@tanstack/react-query": "^5.59.0",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "expo": "~54.0.0",
- "expo-asset": "~12.0.10",
- "expo-blur": "^15.0.8",
- "expo-build-properties": "~1.0.9",
- "expo-camera": "^17.0.10",
- "expo-constants": "~18.0.10",
- "expo-dev-client": "~6.0.19",
- "expo-document-picker": "~14.0.7",
- "expo-file-system": "~19.0.19",
- "expo-haptics": "~15.0.7",
- "expo-image": "~3.0.10",
- "expo-linking": "~8.0.9",
- "expo-router": "~6.0.0",
- "expo-splash-screen": "~31.0.11",
- "expo-status-bar": "~3.0.8",
- "nativewind": "^4.1.23",
- "phosphor-react-native": "^2.1.0",
- "react": "19.1.0",
- "react-native": "0.81.5",
- "react-native-gesture-handler": "~2.28.0",
- "react-native-qrcode-svg": "^6.3.21",
- "react-native-reanimated": "~4.1.1",
- "react-native-safe-area-context": "~5.6.0",
- "react-native-screens": "~4.16.0",
- "react-native-svg": "15.12.1",
- "react-native-worklets": "^0.7.1",
- "sd-mobile-core": "file:./modules/sd-mobile-core",
- "zustand": "^5.0.2"
- },
- "devDependencies": {
- "@babel/core": "^7.26.0",
- "@babel/plugin-transform-runtime": "^7.28.5",
- "@babel/runtime": "^7.28.4",
- "babel-preset-expo": "~54.0.0",
- "eslint": "^9.15.0",
- "prettier": "^3.3.3",
- "react-native-svg-transformer": "^1.5.0",
- "tailwindcss": "^3.4.15",
- "typescript": "^5.6.3"
- }
-}
+ "name": "@sd/mobile",
+ "version": "0.1.0",
+ "private": true,
+ "main": "expo-router/entry",
+ "scripts": {
+ "start": "bunx expo start",
+ "android": "bunx expo run:android",
+ "ios": "bunx expo run:ios",
+ "patch": "./scripts/patch-svg.sh",
+ "prebuild": "bun run patch && bunx expo prebuild",
+ "prebuild:clean": "bun run patch && bunx expo prebuild --clean",
+ "xcode": "open ios/Spacedrive.xcworkspace",
+ "android-studio": "open -a '/Applications/Android Studio.app' ./android",
+ "data": "open \"$(xcrun simctl get_app_container booted com.spacedrive.app data)\"",
+ "lint": "eslint src --cache",
+ "typecheck": "tsc -b"
+ },
+ "dependencies": {
+ "@callstack/liquid-glass": "^0.7.0",
+ "@dev-plugins/react-query": "^0.4.0",
+ "@gorhom/bottom-sheet": "^5.0.6",
+ "@react-native-async-storage/async-storage": "^2.1.0",
+ "@react-native-clipboard/clipboard": "^1.16.3",
+ "@react-native-community/slider": "^5.1.1",
+ "@react-navigation/bottom-tabs": "^7.2.0",
+ "@react-navigation/drawer": "^7.1.1",
+ "@react-navigation/native": "^7.0.14",
+ "@react-navigation/native-stack": "^7.2.0",
+ "@sd/assets": "workspace:*",
+ "@sd/ts-client": "workspace:*",
+ "@spacedrive/tokens": "^0.2.3",
+ "@shopify/flash-list": "2.0.2",
+ "@tanstack/react-query": "^5.59.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "expo": "~54.0.0",
+ "expo-asset": "~12.0.10",
+ "expo-blur": "^15.0.8",
+ "expo-build-properties": "~1.0.9",
+ "expo-camera": "^17.0.10",
+ "expo-constants": "~18.0.10",
+ "expo-dev-client": "~6.0.19",
+ "expo-document-picker": "~14.0.7",
+ "expo-file-system": "~19.0.19",
+ "expo-haptics": "~15.0.7",
+ "expo-image": "~3.0.10",
+ "expo-linking": "~8.0.9",
+ "expo-router": "~6.0.0",
+ "expo-splash-screen": "~31.0.11",
+ "expo-status-bar": "~3.0.8",
+ "nativewind": "^4.1.23",
+ "phosphor-react-native": "^2.1.0",
+ "react": "19.1.0",
+ "react-native": "0.81.5",
+ "react-native-gesture-handler": "~2.28.0",
+ "react-native-qrcode-svg": "^6.3.21",
+ "react-native-reanimated": "~4.1.1",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0",
+ "react-native-svg": "15.12.1",
+ "react-native-worklets": "^0.7.1",
+ "sd-mobile-core": "file:./modules/sd-mobile-core",
+ "zustand": "^5.0.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.26.0",
+ "@babel/plugin-transform-runtime": "^7.28.5",
+ "@babel/runtime": "^7.28.4",
+ "babel-preset-expo": "~54.0.0",
+ "eslint": "^9.15.0",
+ "prettier": "^3.3.3",
+ "react-native-svg-transformer": "^1.5.0",
+ "tailwindcss": "^3.4.15",
+ "typescript": "^5.6.3"
+ }
+}
\ No newline at end of file
diff --git a/apps/mobile/src/components/PageIndicator.tsx b/apps/mobile/src/components/PageIndicator.tsx
index ae273b8e10c8..a6b874d841b2 100644
--- a/apps/mobile/src/components/PageIndicator.tsx
+++ b/apps/mobile/src/components/PageIndicator.tsx
@@ -1,6 +1,5 @@
import React from "react";
import { View } from "react-native";
-import sharedColors from "@sd/ui/style/colors";
interface PageIndicatorProps {
currentIndex: number;
@@ -14,8 +13,8 @@ interface PageIndicatorProps {
export function PageIndicator({
currentIndex,
totalPages,
- activeColor = `hsl(${sharedColors.accent.DEFAULT})`,
- inactiveColor = `hsl(${sharedColors.app.line})`,
+ activeColor = "hsl(208, 100%, 57%)",
+ inactiveColor = "hsl(235, 15%, 23%)",
pageColors,
}: PageIndicatorProps) {
return (
diff --git a/apps/mobile/src/screens/browse/BrowseScreen.tsx b/apps/mobile/src/screens/browse/BrowseScreen.tsx
index 6d201dc750ca..a3cbc43a39df 100644
--- a/apps/mobile/src/screens/browse/BrowseScreen.tsx
+++ b/apps/mobile/src/screens/browse/BrowseScreen.tsx
@@ -19,7 +19,6 @@ import { useNormalizedQuery } from "../../client";
import { PageIndicator } from "../../components/PageIndicator";
import { GlassSearchBar } from "../../components/GlassSearchBar";
import { useRouter } from "expo-router";
-import sharedColors from "@sd/ui/style/colors";
import type { SpaceItem, SpaceGroup } from "@sd/ts-client";
import { SpaceItem as SpaceItemComponent, SpaceGroupComponent } from "./components";
import { SettingsGroup } from "../../components/primitive";
@@ -178,7 +177,7 @@ export function BrowseScreen() {
// Build page colors array - space colors for space pages, accent for create page
const pageColors = [
...spacesList.map((space) => space.color),
- `hsl(${sharedColors.accent.DEFAULT})`, // Create page uses accent color
+ "hsl(208, 100%, 57%)", // Create page uses accent color
];
return (
diff --git a/apps/mobile/tailwind.config.js b/apps/mobile/tailwind.config.js
index 83e960922e9f..3080cc4729b6 100644
--- a/apps/mobile/tailwind.config.js
+++ b/apps/mobile/tailwind.config.js
@@ -1,4 +1,4 @@
-const sharedColors = require('@sd/ui/style/colors');
+const sharedColors = require('@spacedrive/tokens/raw-colors');
/**
* Convert shared color format (HSL string) to NativeWind format (hsl() function)
diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml
index 6b51678ddb6b..18bec306bf7c 100644
--- a/apps/server/Cargo.toml
+++ b/apps/server/Cargo.toml
@@ -20,8 +20,14 @@ axum = "0.7"
axum-extra = { version = "0.9", features = ["typed-header"] }
http = "1.1"
tokio = { version = "1", features = ["rt-multi-thread", "signal", "sync", "io-util"] }
+tokio-stream = "0.1"
tower = "0.4"
tower-http = { version = "0.5", features = ["fs", "cors"] }
+futures = "0.3"
+
+# Embedded web UI
+rust-embed = "8"
+mime_guess = "2"
# Auth
secstr = "0.5"
diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile
index 3397106e59cd..9d1196ea396f 100644
--- a/apps/server/Dockerfile
+++ b/apps/server/Dockerfile
@@ -1,5 +1,10 @@
# Spacedrive Server Docker Image
-# Single-stage build for RPC-only server (no web UI)
+# Builds sd-server with the apps/web bundle embedded via rust-embed.
+#
+# Prerequisite: `apps/web/dist/` must exist in the build context — run
+# `bun install && bun run build` in `apps/web/` before `docker build`.
+# CI workflows and the unified spacedrive+spacebot image handle this
+# automatically; this Dockerfile assumes the dist directory is ready.
FROM debian:bookworm-slim AS builder
@@ -35,6 +40,9 @@ COPY core ./core
COPY crates ./crates
COPY apps/server ./apps/server
+# Embedded web UI assets — must be pre-built before `docker build` runs.
+COPY apps/web/dist ./apps/web/dist
+
# Build server with media processing features
RUN --mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
diff --git a/apps/server/README.md b/apps/server/README.md
index 133697331aaa..8b5483fb7117 100644
--- a/apps/server/README.md
+++ b/apps/server/README.md
@@ -281,4 +281,4 @@ apps/server/
## License
-AGPL-3.0 - See LICENSE file in repository root.
+FSL-1.1-ALv2 - See LICENSE file in repository root.
diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs
index a4503e88d572..3eaf0b2d6bd3 100644
--- a/apps/server/src/main.rs
+++ b/apps/server/src/main.rs
@@ -1,23 +1,41 @@
use axum::{
+ body::Body,
extract::{FromRequestParts, Request, State},
- http::StatusCode,
+ http::{header, StatusCode, Uri},
middleware::{self, Next},
- response::{IntoResponse, Response},
+ response::{
+ sse::{Event as SseEvent, KeepAlive, Sse},
+ IntoResponse, Response,
+ },
routing::{get, post},
Json, Router,
};
use axum_extra::{headers::authorization::Basic, headers::Authorization, TypedHeader};
use clap::Parser;
+use futures::stream::{Stream, StreamExt};
+use rust_embed::Embed;
use secstr::SecStr;
-use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
+use std::{
+ collections::HashMap, convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc,
+ time::Duration,
+};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::TcpStream,
signal,
- sync::RwLock,
+ sync::{mpsc, RwLock},
};
+use tokio_stream::wrappers::ReceiverStream;
use tracing::{info, warn};
+/// Embedded web UI assets, built from `apps/web` via `bun run build`.
+/// In debug builds, files are read from disk at request time, so editing
+/// `apps/web/dist/` after a rebuild of the frontend is picked up live.
+/// In release builds, contents are baked into the binary.
+#[derive(Embed)]
+#[folder = "../web/dist/"]
+struct WebAssets;
+
#[derive(Clone)]
struct AppState {
auth: HashMap,
@@ -66,6 +84,127 @@ async fn health() -> &'static str {
"OK"
}
+/// Serve the embedded web UI. Looks up the requested path in `WebAssets`;
+/// if not found, falls back to `index.html` so client-side routing in the
+/// SPA continues to work for deep links like `/explorer/foo/bar`.
+async fn serve_web(uri: Uri) -> Response {
+ let path = uri.path().trim_start_matches('/');
+ let lookup = if path.is_empty() { "index.html" } else { path };
+
+ if let Some(asset) = WebAssets::get(lookup) {
+ let mime = mime_guess::from_path(lookup).first_or_octet_stream();
+ return Response::builder()
+ .header(header::CONTENT_TYPE, mime.as_ref())
+ .body(Body::from(asset.data.into_owned()))
+ .expect("static asset response is well-formed");
+ }
+
+ if let Some(index) = WebAssets::get("index.html") {
+ return Response::builder()
+ .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
+ .body(Body::from(index.data.into_owned()))
+ .expect("index.html response is well-formed");
+ }
+
+ // Web bundle is missing entirely — sd-server was built without `apps/web/dist`.
+ Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
+ .body(Body::from(
+ "Spacedrive web UI is not bundled in this build. \
+ Run `bun run build` in `apps/web/` and rebuild sd-server.",
+ ))
+ .expect("missing-bundle response is well-formed")
+}
+
+/// Bridge the daemon's event stream to a browser SSE connection.
+///
+/// Opens a dedicated TCP connection to the daemon, sends a Subscribe request
+/// covering the full set of broadcast events, and forwards each Event /
+/// LogMessage line as an SSE message. The browser receives a continuous
+/// stream of typed JSON payloads as long as the connection is held open.
+///
+/// When the SSE client disconnects, the spawned task's send fails and the
+/// task exits, dropping the daemon TCP connection.
+async fn events_sse(
+ State(state): State,
+) -> Sse>> {
+ let (tx, rx) = mpsc::channel::(64);
+ let socket_addr = state.socket_addr.clone();
+
+ tokio::spawn(async move {
+ if let Err(e) = bridge_daemon_events(socket_addr, tx).await {
+ tracing::warn!("event bridge ended: {}", e);
+ }
+ });
+
+ let stream = ReceiverStream::new(rx)
+ .map(|line| Ok::(SseEvent::default().data(line)));
+
+ Sse::new(stream).keep_alive(
+ KeepAlive::new()
+ .interval(Duration::from_secs(15))
+ .text("keep-alive"),
+ )
+}
+
+/// Connect to the daemon socket, subscribe to its event stream, and forward
+/// each Event/LogMessage line into the channel. Returns Err on transport
+/// failure or when the receiver is dropped.
+async fn bridge_daemon_events(
+ socket_addr: String,
+ tx: mpsc::Sender,
+) -> Result<(), Box> {
+ let stream = TcpStream::connect(&socket_addr).await?;
+ let (reader, mut writer) = stream.into_split();
+
+ // Subscribe with empty event_types meaning "all", and no filter.
+ let subscribe = serde_json::json!({
+ "Subscribe": {
+ "event_types": [],
+ "filter": null,
+ }
+ });
+ let line = serde_json::to_string(&subscribe)?;
+ writer.write_all(line.as_bytes()).await?;
+ writer.write_all(b"\n").await?;
+
+ let mut reader = BufReader::new(reader);
+ let mut buf = String::new();
+
+ loop {
+ buf.clear();
+ let n = reader.read_line(&mut buf).await?;
+ if n == 0 {
+ // Daemon closed the connection.
+ return Ok(());
+ }
+ let trimmed = buf.trim();
+ if trimmed.is_empty() {
+ continue;
+ }
+
+ // Forward only Event/LogMessage lines; skip Subscribed/Unsubscribed
+ // acks and anything else the daemon might emit.
+ match serde_json::from_str::(trimmed) {
+ Ok(value) => {
+ let is_payload = value.get("Event").is_some() || value.get("LogMessage").is_some();
+ if !is_payload {
+ continue;
+ }
+ if tx.send(trimmed.to_string()).await.is_err() {
+ // Receiver dropped — client disconnected.
+ return Ok(());
+ }
+ }
+ Err(e) => {
+ tracing::debug!("daemon emitted non-JSON line: {}", e);
+ continue;
+ }
+ }
+ }
+}
+
/// Proxy RPC requests to the daemon via TCP
async fn daemon_rpc(
State(state): State,
@@ -210,16 +349,8 @@ async fn main() -> Result<(), Box> {
let app = Router::new()
.route("/health", get(health))
.route("/rpc", post(daemon_rpc))
- .route(
- "/",
- get(|| async { "Spacedrive Server - RPC only (no web UI)" }),
- )
- .fallback(|| async {
- (
- StatusCode::NOT_FOUND,
- "404 Not Found: We're past the event horizon...",
- )
- })
+ .route("/events", get(events_sse))
+ .fallback(serve_web)
.layer(middleware::from_fn_with_state(state.clone(), basic_auth))
.with_state(state);
@@ -231,6 +362,7 @@ async fn main() -> Result<(), Box> {
"Spacedrive Server listening on http://localhost:{}",
args.port
);
+ info!("Web UI available at /");
info!("RPC endpoint available at /rpc");
// Setup graceful shutdown
@@ -310,19 +442,21 @@ async fn start_daemon_if_needed(
}
});
- // Wait for daemon to be ready
- for i in 0..30 {
+ // Wait for daemon to be ready. Networking init (Iroh + relays) can take a
+ // while when relays are unreachable, so we give it a generous window before
+ // failing — better to wait than to spuriously crash on a flaky relay.
+ for i in 0..300 {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
if TcpStream::connect(&socket_addr).await.is_ok() {
info!("✓ Daemon started successfully");
return Ok(Some(Arc::new(RwLock::new(handle))));
}
- if i == 10 {
+ if i == 30 {
warn!("Daemon taking longer than expected to start...");
}
}
- Err("Daemon failed to start (connection not available after 3 seconds)".into())
+ Err("Daemon failed to start (connection not available after 30 seconds)".into())
}
/// Check if daemon is running by sending a ping
diff --git a/apps/tauri/Spacedrive.icon/Assets/Ball.png b/apps/tauri/Spacedrive.icon/Assets/Ball.png
deleted file mode 100644
index db641ae5cec0..000000000000
Binary files a/apps/tauri/Spacedrive.icon/Assets/Ball.png and /dev/null differ
diff --git a/apps/tauri/Spacedrive.icon/Assets/spacedrive.png b/apps/tauri/Spacedrive.icon/Assets/spacedrive.png
new file mode 100644
index 000000000000..085d001dad8c
Binary files /dev/null and b/apps/tauri/Spacedrive.icon/Assets/spacedrive.png differ
diff --git a/apps/tauri/Spacedrive.icon/icon.json b/apps/tauri/Spacedrive.icon/icon.json
index 86ee04bd42dc..cb4d1e6b3825 100644
--- a/apps/tauri/Spacedrive.icon/icon.json
+++ b/apps/tauri/Spacedrive.icon/icon.json
@@ -1,80 +1,16 @@
{
- "fill-specializations" : [
- {
- "value" : "automatic"
- },
- {
- "appearance" : "dark",
- "value" : "system-dark"
- }
- ],
+ "fill" : "automatic",
"groups" : [
{
"layers" : [
{
- "blend-mode-specializations" : [
- {
- "appearance" : "tinted",
- "value" : "screen"
- }
- ],
- "fill-specializations" : [
- {
- "appearance" : "tinted",
- "value" : {
- "solid" : "display-p3:1.00000,0.72781,0.41766,1.00000"
- }
- }
- ],
- "glass" : true,
- "hidden" : false,
- "image-name" : "Ball.png",
- "name" : "Ball",
- "opacity-specializations" : [
- {
- "value" : 0.4
- },
- {
- "appearance" : "dark",
- "value" : 0
- },
- {
- "appearance" : "tinted",
- "value" : 0.53
- }
- ],
- "position" : {
- "scale" : 2,
- "translation-in-points" : [
- 1.7218333746113785,
- 2.7640092574830533
- ]
- }
- },
- {
- "blend-mode-specializations" : [
- {
- "appearance" : "tinted",
- "value" : "normal"
- }
- ],
- "fill-specializations" : [
- {
- "appearance" : "tinted",
- "value" : {
- "solid" : "display-p3:1.00000,0.72781,0.41766,1.00000"
- }
- }
- ],
- "glass" : true,
- "hidden" : false,
- "image-name" : "Ball.png",
- "name" : "Ball",
+ "image-name" : "spacedrive.png",
+ "name" : "spacedrive",
"position" : {
- "scale" : 2,
+ "scale" : 2.86,
"translation-in-points" : [
- 1.7218333746113785,
- 2.7640092574830533
+ 1.4300000000000637,
+ 1.4300000000000637
]
}
}
diff --git a/apps/tauri/assets/exports/Icon-iOS-ClearDark-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-ClearDark-1024x1024@1x.png
new file mode 100644
index 000000000000..31d354ab96d6
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-ClearDark-1024x1024@1x.png differ
diff --git a/apps/tauri/assets/exports/Icon-iOS-ClearLight-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-ClearLight-1024x1024@1x.png
new file mode 100644
index 000000000000..863143c1b3e7
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-ClearLight-1024x1024@1x.png differ
diff --git a/apps/tauri/assets/exports/Icon-iOS-Dark-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-Dark-1024x1024@1x.png
new file mode 100644
index 000000000000..0d4d37d7a114
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-Dark-1024x1024@1x.png differ
diff --git a/apps/tauri/assets/exports/Icon-iOS-Default-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-Default-1024x1024@1x.png
new file mode 100644
index 000000000000..c08919a7a6bd
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-Default-1024x1024@1x.png differ
diff --git a/apps/tauri/assets/exports/Icon-iOS-TintedDark-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-TintedDark-1024x1024@1x.png
new file mode 100644
index 000000000000..46620855d05a
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-TintedDark-1024x1024@1x.png differ
diff --git a/apps/tauri/assets/exports/Icon-iOS-TintedLight-1024x1024@1x.png b/apps/tauri/assets/exports/Icon-iOS-TintedLight-1024x1024@1x.png
new file mode 100644
index 000000000000..92c22c57d2ca
Binary files /dev/null and b/apps/tauri/assets/exports/Icon-iOS-TintedLight-1024x1024@1x.png differ
diff --git a/apps/tauri/package.json b/apps/tauri/package.json
index 041fadffdf40..71e86e391e4b 100644
--- a/apps/tauri/package.json
+++ b/apps/tauri/package.json
@@ -24,24 +24,29 @@
"@sd/assets": "workspace:*",
"@sd/interface": "workspace:*",
"@sd/ts-client": "workspace:*",
- "@sd/ui": "workspace:*",
+ "@spacedrive/primitives": "^0.2.3",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.0.1",
"@tauri-apps/plugin-shell": "^2.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-router-dom": "=6.20.1",
"react-scan": "^0.4.3"
},
"devDependencies": {
+ "@headlessui/tailwindcss": "^0.2.0",
+ "@spacedrive/tokens": "^0.2.3",
+ "@tailwindcss/forms": "^0.5.7",
+ "@tailwindcss/typography": "^0.5.10",
+ "@tailwindcss/vite": "^4.1.0",
"@tauri-apps/cli": "^2.1.0",
- "@types/react": "npm:types-react@rc",
- "@types/react-dom": "npm:types-react-dom@rc",
- "@vitejs/plugin-react-swc": "^3.7.1",
- "autoprefixer": "^10.4.18",
- "postcss": "^8.4.36",
- "tailwindcss": "^3.4.1",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3",
+ "tailwindcss": "^4.1.0",
+ "tailwindcss-animate": "^1.0.7",
+ "tailwindcss-radix": "^2.8.0",
"typescript": "^5.6.2",
"vite": "^5.4.9"
}
-}
+}
\ No newline at end of file
diff --git a/apps/tauri/postcss.config.cjs b/apps/tauri/postcss.config.cjs
deleted file mode 100644
index e873f1a4f235..000000000000
--- a/apps/tauri/postcss.config.cjs
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
diff --git a/apps/tauri/scripts/dev-with-daemon.ts b/apps/tauri/scripts/dev-with-daemon.ts
index 5b691063764d..f9ed7569d5ed 100755
--- a/apps/tauri/scripts/dev-with-daemon.ts
+++ b/apps/tauri/scripts/dev-with-daemon.ts
@@ -8,50 +8,46 @@
* 4. Starts Vite dev server
* 5. Cleans up daemon on exit
*/
-
-import { spawn, execSync } from "child_process";
-import { existsSync, unlinkSync } from "fs";
-import { join, resolve, dirname } from "path";
-import { homedir, platform } from "os";
-import { fileURLToPath } from "url";
+import {execSync, spawn} from 'child_process';
+import {existsSync, unlinkSync} from 'fs';
+import {homedir, platform} from 'os';
+import {dirname, join, resolve} from 'path';
+import {fileURLToPath} from 'url';
// Get script directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Detect Platform
-const IS_WIN = platform() === "win32";
+const IS_WIN = platform() === 'win32';
// Paths relative to this script (apps/tauri/scripts/)
// Script is at: PROJECT_ROOT/apps/tauri/scripts/
// So PROJECT_ROOT is: ../../../
-const PROJECT_ROOT = resolve(__dirname, "../../../");
+const PROJECT_ROOT = resolve(__dirname, '../../../');
// Resolve target directory from Cargo config (supports custom target-dir)
function getCargoTargetDir(): string {
- try {
- const output = execSync("cargo metadata --format-version 1 --no-deps", {
- cwd: PROJECT_ROOT,
- encoding: "utf8",
- stdio: ["pipe", "pipe", "pipe"],
- });
- const metadata = JSON.parse(output);
- return metadata.target_directory;
- } catch {
- return join(PROJECT_ROOT, "target");
- }
+ try {
+ const output = execSync('cargo metadata --format-version 1 --no-deps', {
+ cwd: PROJECT_ROOT,
+ encoding: 'utf8',
+ stdio: ['pipe', 'pipe', 'pipe']
+ });
+ const metadata = JSON.parse(output);
+ return metadata.target_directory;
+ } catch {
+ return join(PROJECT_ROOT, 'target');
+ }
}
-const BIN_NAME = IS_WIN ? "sd-daemon.exe" : "sd-daemon";
-const DAEMON_BIN = join(getCargoTargetDir(), "debug", BIN_NAME);
+const BIN_NAME = IS_WIN ? 'sd-daemon.exe' : 'sd-daemon';
+const DAEMON_BIN = join(getCargoTargetDir(), 'debug', BIN_NAME);
const DAEMON_PORT = 6969;
const DAEMON_ADDR = `127.0.0.1:${DAEMON_PORT}`;
-// Fix Data Directory for Windows (Optional but recommended)
-const DATA_DIR = IS_WIN
- ? join(homedir(), "AppData/Roaming/spacedrive")
- : join(homedir(), "Library/Application Support/spacedrive");
+const DATA_DIR = join(homedir(), '.spacedrive');
let daemonProcess: any = null;
let viteProcess: any = null;
@@ -59,158 +55,162 @@ let startedDaemon = false;
// Cleanup function
function cleanup() {
- console.log("\nCleaning up...");
+ console.log('\nCleaning up...');
- if (viteProcess) {
- console.log("Stopping Vite...");
- viteProcess.kill();
- }
+ if (viteProcess) {
+ console.log('Stopping Vite...');
+ viteProcess.kill();
+ }
- if (daemonProcess && startedDaemon) {
- console.log("Stopping daemon (started by us)...");
- daemonProcess.kill();
- } else if (!startedDaemon) {
- console.log("Leaving existing daemon running...");
- }
+ if (daemonProcess && startedDaemon) {
+ console.log('Stopping daemon (started by us)...');
+ daemonProcess.kill();
+ } else if (!startedDaemon) {
+ console.log('Leaving existing daemon running...');
+ }
- process.exit(0);
+ process.exit(0);
}
// Handle signals
-process.on("SIGINT", cleanup);
-process.on("SIGTERM", cleanup);
+process.on('SIGINT', cleanup);
+process.on('SIGTERM', cleanup);
async function main() {
- // Check if daemon is already running by trying to connect to TCP port
- let daemonAlreadyRunning = false;
- console.log(`Checking if daemon is running on ${DAEMON_ADDR}...`);
- try {
- const { connect } = await import("net");
- await new Promise((resolve, reject) => {
- const client = connect(DAEMON_PORT, "127.0.0.1");
- client.on("connect", () => {
- daemonAlreadyRunning = true;
- client.end();
- resolve();
- });
- client.on("error", () => {
- reject();
- });
- setTimeout(() => reject(), 1000);
- });
- } catch (e) {
- // Connection failed, daemon not running
- daemonAlreadyRunning = false;
- }
-
- if (daemonAlreadyRunning) {
- console.log("Daemon already running, skipping build and using existing instance");
- startedDaemon = false;
- } else {
- console.log("Building daemon (dev profile)...");
- console.log("Project root:", PROJECT_ROOT);
- console.log("Daemon binary:", DAEMON_BIN);
-
- // Build daemon
- // On Windows, the binary target name is still just "sd-daemon" (Cargo handles the .exe)
- const build = spawn("cargo", ["build", "--bin", "sd-daemon"], {
- cwd: PROJECT_ROOT,
- stdio: "inherit",
- shell: IS_WIN, // shell: true is often needed on Windows for spawn to work correctly
- });
-
- await new Promise((resolve, reject) => {
- build.on("exit", (code) => {
- if (code === 0) {
- resolve();
- } else {
- reject(new Error(`Daemon build failed with code ${code}`));
- }
- });
- });
-
- console.log("Daemon built successfully");
- // Start daemon
- console.log("Starting daemon...");
- startedDaemon = true;
-
- // Verify binary exists
- if (!existsSync(DAEMON_BIN)) {
- throw new Error(`Daemon binary not found at: ${DAEMON_BIN}`);
- }
-
- const depsLibPath = join(PROJECT_ROOT, "apps/.deps/lib");
- const depsBinPath = join(PROJECT_ROOT, "apps/.deps/bin");
-
- daemonProcess = spawn(DAEMON_BIN, ["--data-dir", DATA_DIR], {
- cwd: PROJECT_ROOT,
- stdio: ["ignore", "pipe", "pipe"],
- env: {
- ...process.env,
- // macOS library path
- DYLD_LIBRARY_PATH: depsLibPath,
- // Windows: Add DLLs directory to PATH
- PATH: IS_WIN
- ? `${depsBinPath};${process.env.PATH || ""}`
- : process.env.PATH,
- },
- });
-
- // Log daemon output
- daemonProcess.stdout.on("data", (data: Buffer) => {
- const lines = data.toString().trim().split("\n");
- for (const line of lines) {
- console.log(`[daemon] ${line}`);
- }
- });
-
- daemonProcess.stderr.on("data", (data: Buffer) => {
- const lines = data.toString().trim().split("\n");
- for (const line of lines) {
- console.log(`[daemon] ${line}`);
- }
- });
-
- // Wait for daemon to be ready
- console.log("Waiting for daemon to be ready...");
- for (let i = 0; i < 30; i++) {
- try {
- const { connect } = await import("net");
- await new Promise((resolve, reject) => {
- const client = connect(DAEMON_PORT, "127.0.0.1");
- client.on("connect", () => {
- client.end();
- resolve();
- });
- client.on("error", reject);
- setTimeout(() => reject(), 500);
- });
- console.log(`Daemon ready at ${DAEMON_ADDR}`);
- break;
- } catch (e) {
- if (i === 29) {
- throw new Error("Daemon failed to start (connection not available)");
- }
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
- }
- }
-
- // Start Vite
- console.log("Starting Vite dev server...");
-
- // Use 'bun' explicitly, with shell true for Windows compatibility
- viteProcess = spawn("bun", ["run", "dev"], {
- stdio: "inherit",
- shell: IS_WIN,
- });
-
- // Keep running
- await new Promise(() => {});
+ // Check if daemon is already running by trying to connect to TCP port
+ let daemonAlreadyRunning = false;
+ console.log(`Checking if daemon is running on ${DAEMON_ADDR}...`);
+ try {
+ const {connect} = await import('net');
+ await new Promise((resolve, reject) => {
+ const client = connect(DAEMON_PORT, '127.0.0.1');
+ client.on('connect', () => {
+ daemonAlreadyRunning = true;
+ client.end();
+ resolve();
+ });
+ client.on('error', () => {
+ reject();
+ });
+ setTimeout(() => reject(), 1000);
+ });
+ } catch (e) {
+ // Connection failed, daemon not running
+ daemonAlreadyRunning = false;
+ }
+
+ if (daemonAlreadyRunning) {
+ console.log(
+ 'Daemon already running, skipping build and using existing instance'
+ );
+ startedDaemon = false;
+ } else {
+ console.log('Building daemon (dev profile)...');
+ console.log('Project root:', PROJECT_ROOT);
+ console.log('Daemon binary:', DAEMON_BIN);
+
+ // Build daemon
+ // On Windows, the binary target name is still just "sd-daemon" (Cargo handles the .exe)
+ const build = spawn('cargo', ['build', '--bin', 'sd-daemon'], {
+ cwd: PROJECT_ROOT,
+ stdio: 'inherit',
+ shell: IS_WIN // shell: true is often needed on Windows for spawn to work correctly
+ });
+
+ await new Promise((resolve, reject) => {
+ build.on('exit', (code) => {
+ if (code === 0) {
+ resolve();
+ } else {
+ reject(new Error(`Daemon build failed with code ${code}`));
+ }
+ });
+ });
+
+ console.log('Daemon built successfully');
+ // Start daemon
+ console.log('Starting daemon...');
+ startedDaemon = true;
+
+ // Verify binary exists
+ if (!existsSync(DAEMON_BIN)) {
+ throw new Error(`Daemon binary not found at: ${DAEMON_BIN}`);
+ }
+
+ const depsLibPath = join(PROJECT_ROOT, 'apps/.deps/lib');
+ const depsBinPath = join(PROJECT_ROOT, 'apps/.deps/bin');
+
+ daemonProcess = spawn(DAEMON_BIN, ['--data-dir', DATA_DIR], {
+ cwd: PROJECT_ROOT,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ env: {
+ ...process.env,
+ // macOS library path
+ DYLD_LIBRARY_PATH: depsLibPath,
+ // Windows: Add DLLs directory to PATH
+ PATH: IS_WIN
+ ? `${depsBinPath};${process.env.PATH || ''}`
+ : process.env.PATH
+ }
+ });
+
+ // Log daemon output
+ daemonProcess.stdout.on('data', (data: Buffer) => {
+ const lines = data.toString().trim().split('\n');
+ for (const line of lines) {
+ console.log(`[daemon] ${line}`);
+ }
+ });
+
+ daemonProcess.stderr.on('data', (data: Buffer) => {
+ const lines = data.toString().trim().split('\n');
+ for (const line of lines) {
+ console.log(`[daemon] ${line}`);
+ }
+ });
+
+ // Wait for daemon to be ready
+ console.log('Waiting for daemon to be ready...');
+ for (let i = 0; i < 30; i++) {
+ try {
+ const {connect} = await import('net');
+ await new Promise((resolve, reject) => {
+ const client = connect(DAEMON_PORT, '127.0.0.1');
+ client.on('connect', () => {
+ client.end();
+ resolve();
+ });
+ client.on('error', reject);
+ setTimeout(() => reject(), 500);
+ });
+ console.log(`Daemon ready at ${DAEMON_ADDR}`);
+ break;
+ } catch (e) {
+ if (i === 29) {
+ throw new Error(
+ 'Daemon failed to start (connection not available)'
+ );
+ }
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ }
+ }
+
+ // Start Vite
+ console.log('Starting Vite dev server...');
+
+ // Use 'bun' explicitly, with shell true for Windows compatibility
+ viteProcess = spawn('bun', ['run', 'dev'], {
+ stdio: 'inherit',
+ shell: IS_WIN
+ });
+
+ // Keep running
+ await new Promise(() => {});
}
main().catch((error) => {
- console.error("Error:", error);
- cleanup();
- process.exit(1);
-});
\ No newline at end of file
+ console.error('Error:', error);
+ cleanup();
+ process.exit(1);
+});
diff --git a/apps/tauri/sd-tauri-core/src/lib.rs b/apps/tauri/sd-tauri-core/src/lib.rs
index b56428d7ca09..294b45d72f10 100644
--- a/apps/tauri/sd-tauri-core/src/lib.rs
+++ b/apps/tauri/sd-tauri-core/src/lib.rs
@@ -41,22 +41,11 @@ pub mod commands {
// Following the pattern from sd-ios-core but for Tauri's IPC
}
-/// Platform-specific data directory resolution
+/// Default data directory: `~/.spacedrive`
pub fn default_data_dir() -> anyhow::Result {
- #[cfg(target_os = "macos")]
- let dir = dirs::data_dir()
- .ok_or_else(|| anyhow::anyhow!("Could not determine data directory"))?
- .join("spacedrive");
-
- #[cfg(target_os = "windows")]
- let dir = dirs::data_dir()
- .ok_or_else(|| anyhow::anyhow!("Could not determine data directory"))?
- .join("Spacedrive");
-
- #[cfg(target_os = "linux")]
- let dir = dirs::data_local_dir()
- .ok_or_else(|| anyhow::anyhow!("Could not determine data directory"))?
- .join("spacedrive");
+ let dir = dirs::home_dir()
+ .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?
+ .join(".spacedrive");
// Create directory if it doesn't exist
std::fs::create_dir_all(&dir)?;
diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml
index 84b8262bdcc1..23b27b905a94 100644
--- a/apps/tauri/src-tauri/Cargo.toml
+++ b/apps/tauri/src-tauri/Cargo.toml
@@ -17,6 +17,7 @@ tauri-plugin-dialog = "2.0"
tauri-plugin-fs = "2.0"
tauri-plugin-shell = "2.0"
tauri-plugin-clipboard-manager = "2.0"
+tauri-plugin-global-shortcut = "2.0"
tauri-plugin-os = "2.0"
tauri-plugin-updater = "2.0"
diff --git a/apps/tauri/src-tauri/build.rs b/apps/tauri/src-tauri/build.rs
index ffc4afae9907..888ec7e00c10 100644
--- a/apps/tauri/src-tauri/build.rs
+++ b/apps/tauri/src-tauri/build.rs
@@ -74,19 +74,22 @@ fn main() {
""
};
- let daemon_source = format!("{}/target/{}/sd-daemon{}", workspace_dir, profile, exe_ext);
- let daemon_target = format!(
- "{}/target/{}/sd-daemon-{}{}",
- workspace_dir, profile, target_triple, exe_ext
- );
+ for source_profile in [profile.as_str(), "release"] {
+ let daemon_source = format!(
+ "{}/target/{}/sd-daemon{}",
+ workspace_dir, source_profile, exe_ext
+ );
+ let daemon_target = format!(
+ "{}/target/{}/sd-daemon-{}{}",
+ workspace_dir, source_profile, target_triple, exe_ext
+ );
- if std::path::Path::new(&daemon_source).exists() {
- // Remove existing file if it exists
- let _ = std::fs::remove_file(&daemon_target);
+ if std::path::Path::new(&daemon_source).exists() {
+ let _ = std::fs::remove_file(&daemon_target);
- // Copy the daemon binary with target architecture suffix
- if let Err(e) = std::fs::copy(&daemon_source, &daemon_target) {
- eprintln!("Warning: Failed to copy daemon: {}", e);
+ if let Err(e) = std::fs::copy(&daemon_source, &daemon_target) {
+ eprintln!("Warning: Failed to copy daemon: {}", e);
+ }
}
}
diff --git a/apps/tauri/src-tauri/capabilities/default.json b/apps/tauri/src-tauri/capabilities/default.json
index 9206c480571c..544008a662d0 100644
--- a/apps/tauri/src-tauri/capabilities/default.json
+++ b/apps/tauri/src-tauri/capabilities/default.json
@@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for Spacedrive",
- "windows": ["main", "inspector-*", "quick-preview-*", "settings-*", "job-manager"],
+ "windows": ["main", "spacebot", "voice-overlay", "inspector-*", "quick-preview-*", "settings-*", "job-manager"],
"permissions": [
"core:default",
"core:event:allow-listen",
diff --git a/apps/tauri/src-tauri/gen/schemas/acl-manifests.json b/apps/tauri/src-tauri/gen/schemas/acl-manifests.json
index 22a84cee2c22..b6a016fd7fa8 100644
--- a/apps/tauri/src-tauri/gen/schemas/acl-manifests.json
+++ b/apps/tauri/src-tauri/gen/schemas/acl-manifests.json
@@ -1 +1 @@
-{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n","permissions":["create-app-specific-dirs","read-app-specific-dirs-recursive","deny-default"]},"permissions":{"allow-copy-file":{"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-exists":{"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]}},"allow-fstat":{"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]}},"allow-ftruncate":{"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]}},"allow-lstat":{"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]}},"allow-mkdir":{"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-dir":{"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]}},"allow-read-file":{"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]}},"allow-read-text-file":{"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]}},"allow-read-text-file-lines":{"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines","read_text_file_lines_next"],"deny":[]}},"allow-read-text-file-lines-next":{"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-rename":{"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]}},"allow-seek":{"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-truncate":{"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-file":{"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file","open","write"],"deny":[]}},"allow-write-text-file":{"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]}},"create-app-specific-dirs":{"identifier":"create-app-specific-dirs","description":"This permissions allows to create the application specific directories.\n","commands":{"allow":["mkdir","scope-app-index"],"deny":[]}},"deny-copy-file":{"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-exists":{"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]}},"deny-fstat":{"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]}},"deny-ftruncate":{"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]}},"deny-lstat":{"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]}},"deny-mkdir":{"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-dir":{"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]}},"deny-read-file":{"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]}},"deny-read-text-file":{"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]}},"deny-read-text-file-lines":{"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]}},"deny-read-text-file-lines-next":{"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-rename":{"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]}},"deny-seek":{"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-truncate":{"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-webview-data-linux":{"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-webview-data-windows":{"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-file":{"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]}},"deny-write-text-file":{"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]}},"read-all":{"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]}},"read-app-specific-dirs-recursive":{"identifier":"read-app-specific-dirs-recursive","description":"This permission allows recursive read functionality on the application\nspecific base directories. \n","commands":{"allow":["read_dir","read_file","read_text_file","read_text_file_lines","read_text_file_lines_next","exists","scope-app-recursive"],"deny":[]}},"read-dirs":{"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]}},"read-files":{"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]}},"read-meta":{"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists","size"],"deny":[]}},"scope":{"identifier":"scope","description":"An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n","commands":{"allow":[],"deny":[]}},"scope-app":{"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the application folders.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"},{"path":"$APPDATA"},{"path":"$APPDATA/*"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"},{"path":"$APPCACHE"},{"path":"$APPCACHE/*"},{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-app-index":{"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the application directories.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPDATA"},{"path":"$APPLOCALDATA"},{"path":"$APPCACHE"},{"path":"$APPLOG"}]}},"scope-app-recursive":{"identifier":"scope-app-recursive","description":"This scope permits recursive access to the complete application folders, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"},{"path":"$APPDATA"},{"path":"$APPDATA/**"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"},{"path":"$APPCACHE"},{"path":"$APPCACHE/**"},{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-appcache":{"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"}]}},"scope-appcache-recursive":{"identifier":"scope-appcache-recursive","description":"This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"}]}},"scope-appconfig-recursive":{"identifier":"scope-appconfig-recursive","description":"This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"}]}},"scope-appdata-recursive":{"identifier":"scope-appdata-recursive","description":"This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"}]}},"scope-applocaldata-recursive":{"identifier":"scope-applocaldata-recursive","description":"This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-applog-index":{"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"}]}},"scope-applog-recursive":{"identifier":"scope-applog-recursive","description":"This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-audio":{"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/*"}]}},"scope-audio-index":{"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"}]}},"scope-audio-recursive":{"identifier":"scope-audio-recursive","description":"This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/**"}]}},"scope-cache":{"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/*"}]}},"scope-cache-index":{"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"}]}},"scope-cache-recursive":{"identifier":"scope-cache-recursive","description":"This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/**"}]}},"scope-config":{"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/*"}]}},"scope-config-index":{"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"}]}},"scope-config-recursive":{"identifier":"scope-config-recursive","description":"This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/**"}]}},"scope-data":{"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/*"}]}},"scope-data-index":{"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"}]}},"scope-data-recursive":{"identifier":"scope-data-recursive","description":"This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/**"}]}},"scope-desktop":{"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"}]}},"scope-desktop-recursive":{"identifier":"scope-desktop-recursive","description":"This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/**"}]}},"scope-document":{"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"}]}},"scope-document-recursive":{"identifier":"scope-document-recursive","description":"This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/**"}]}},"scope-download":{"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"}]}},"scope-download-recursive":{"identifier":"scope-download-recursive","description":"This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/*"}]}},"scope-exe-index":{"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"}]}},"scope-exe-recursive":{"identifier":"scope-exe-recursive","description":"This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/**"}]}},"scope-font":{"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/*"}]}},"scope-font-index":{"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"}]}},"scope-font-recursive":{"identifier":"scope-font-recursive","description":"This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/**"}]}},"scope-home":{"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/*"}]}},"scope-home-index":{"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"}]}},"scope-home-recursive":{"identifier":"scope-home-recursive","description":"This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/**"}]}},"scope-localdata":{"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"}]}},"scope-localdata-recursive":{"identifier":"scope-localdata-recursive","description":"This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/**"}]}},"scope-log":{"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/*"}]}},"scope-log-index":{"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"}]}},"scope-log-recursive":{"identifier":"scope-log-recursive","description":"This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/**"}]}},"scope-picture":{"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/*"}]}},"scope-picture-index":{"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"}]}},"scope-picture-recursive":{"identifier":"scope-picture-recursive","description":"This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/**"}]}},"scope-public":{"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/*"}]}},"scope-public-index":{"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"}]}},"scope-public-recursive":{"identifier":"scope-public-recursive","description":"This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/**"}]}},"scope-resource":{"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"}]}},"scope-resource-recursive":{"identifier":"scope-resource-recursive","description":"This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/**"}]}},"scope-runtime":{"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"}]}},"scope-runtime-recursive":{"identifier":"scope-runtime-recursive","description":"This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/**"}]}},"scope-temp":{"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/*"}]}},"scope-temp-index":{"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"}]}},"scope-temp-recursive":{"identifier":"scope-temp-recursive","description":"This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/**"}]}},"scope-template":{"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"}]}},"scope-template-recursive":{"identifier":"scope-template-recursive","description":"This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/**"}]}},"scope-video":{"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/*"}]}},"scope-video-index":{"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"}]}},"scope-video-recursive":{"identifier":"scope-video-recursive","description":"This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/**"}]}},"write-all":{"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}},"write-files":{"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows non-recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows full recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the application folders.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete application folders, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the application folders.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recursive write access to the complete application folders, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\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"},{"properties":{"path":{"description":"A path that can be accessed by the webview when using the fs 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"}},"required":["path"],"type":"object"}],"description":"FS scope entry.","title":"FsScopeEntry"}},"os":{"default_permission":{"identifier":"default","description":"This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n","permissions":["allow-arch","allow-exe-extension","allow-family","allow-locale","allow-os-type","allow-platform","allow-version"]},"permissions":{"allow-arch":{"identifier":"allow-arch","description":"Enables the arch command without any pre-configured scope.","commands":{"allow":["arch"],"deny":[]}},"allow-exe-extension":{"identifier":"allow-exe-extension","description":"Enables the exe_extension command without any pre-configured scope.","commands":{"allow":["exe_extension"],"deny":[]}},"allow-family":{"identifier":"allow-family","description":"Enables the family command without any pre-configured scope.","commands":{"allow":["family"],"deny":[]}},"allow-hostname":{"identifier":"allow-hostname","description":"Enables the hostname command without any pre-configured scope.","commands":{"allow":["hostname"],"deny":[]}},"allow-locale":{"identifier":"allow-locale","description":"Enables the locale command without any pre-configured scope.","commands":{"allow":["locale"],"deny":[]}},"allow-os-type":{"identifier":"allow-os-type","description":"Enables the os_type command without any pre-configured scope.","commands":{"allow":["os_type"],"deny":[]}},"allow-platform":{"identifier":"allow-platform","description":"Enables the platform command without any pre-configured scope.","commands":{"allow":["platform"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-arch":{"identifier":"deny-arch","description":"Denies the arch command without any pre-configured scope.","commands":{"allow":[],"deny":["arch"]}},"deny-exe-extension":{"identifier":"deny-exe-extension","description":"Denies the exe_extension command without any pre-configured scope.","commands":{"allow":[],"deny":["exe_extension"]}},"deny-family":{"identifier":"deny-family","description":"Denies the family command without any pre-configured scope.","commands":{"allow":[],"deny":["family"]}},"deny-hostname":{"identifier":"deny-hostname","description":"Denies the hostname command without any pre-configured scope.","commands":{"allow":[],"deny":["hostname"]}},"deny-locale":{"identifier":"deny-locale","description":"Denies the locale command without any pre-configured scope.","commands":{"allow":[],"deny":["locale"]}},"deny-os-type":{"identifier":"deny-os-type","description":"Denies the os_type command without any pre-configured scope.","commands":{"allow":[],"deny":["os_type"]}},"deny-platform":{"identifier":"deny-platform","description":"Denies the platform command without any pre-configured scope.","commands":{"allow":[],"deny":["platform"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}},"updater":{"default_permission":{"identifier":"default","description":"This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n","permissions":["allow-check","allow-download","allow-install","allow-download-and-install"]},"permissions":{"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-download":{"identifier":"allow-download","description":"Enables the download command without any pre-configured scope.","commands":{"allow":["download"],"deny":[]}},"allow-download-and-install":{"identifier":"allow-download-and-install","description":"Enables the download_and_install command without any pre-configured scope.","commands":{"allow":["download_and_install"],"deny":[]}},"allow-install":{"identifier":"allow-install","description":"Enables the install command without any pre-configured scope.","commands":{"allow":["install"],"deny":[]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-download":{"identifier":"deny-download","description":"Denies the download command without any pre-configured scope.","commands":{"allow":[],"deny":["download"]}},"deny-download-and-install":{"identifier":"deny-download-and-install","description":"Denies the download_and_install command without any pre-configured scope.","commands":{"allow":[],"deny":["download_and_install"]}},"deny-install":{"identifier":"deny-install","description":"Denies the install command without any pre-configured scope.","commands":{"allow":[],"deny":["install"]}}},"permission_sets":{},"global_scope_schema":null}}
\ No newline at end of file
+{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n","permissions":["create-app-specific-dirs","read-app-specific-dirs-recursive","deny-default"]},"permissions":{"allow-copy-file":{"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-exists":{"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]}},"allow-fstat":{"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]}},"allow-ftruncate":{"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]}},"allow-lstat":{"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]}},"allow-mkdir":{"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-dir":{"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]}},"allow-read-file":{"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]}},"allow-read-text-file":{"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]}},"allow-read-text-file-lines":{"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines","read_text_file_lines_next"],"deny":[]}},"allow-read-text-file-lines-next":{"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-rename":{"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]}},"allow-seek":{"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-truncate":{"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-file":{"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file","open","write"],"deny":[]}},"allow-write-text-file":{"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]}},"create-app-specific-dirs":{"identifier":"create-app-specific-dirs","description":"This permissions allows to create the application specific directories.\n","commands":{"allow":["mkdir","scope-app-index"],"deny":[]}},"deny-copy-file":{"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-exists":{"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]}},"deny-fstat":{"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]}},"deny-ftruncate":{"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]}},"deny-lstat":{"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]}},"deny-mkdir":{"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-dir":{"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]}},"deny-read-file":{"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]}},"deny-read-text-file":{"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]}},"deny-read-text-file-lines":{"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]}},"deny-read-text-file-lines-next":{"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-rename":{"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]}},"deny-seek":{"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-truncate":{"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-webview-data-linux":{"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-webview-data-windows":{"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-file":{"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]}},"deny-write-text-file":{"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]}},"read-all":{"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]}},"read-app-specific-dirs-recursive":{"identifier":"read-app-specific-dirs-recursive","description":"This permission allows recursive read functionality on the application\nspecific base directories. \n","commands":{"allow":["read_dir","read_file","read_text_file","read_text_file_lines","read_text_file_lines_next","exists","scope-app-recursive"],"deny":[]}},"read-dirs":{"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]}},"read-files":{"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]}},"read-meta":{"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists","size"],"deny":[]}},"scope":{"identifier":"scope","description":"An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n","commands":{"allow":[],"deny":[]}},"scope-app":{"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the application folders.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"},{"path":"$APPDATA"},{"path":"$APPDATA/*"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"},{"path":"$APPCACHE"},{"path":"$APPCACHE/*"},{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-app-index":{"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the application directories.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPDATA"},{"path":"$APPLOCALDATA"},{"path":"$APPCACHE"},{"path":"$APPLOG"}]}},"scope-app-recursive":{"identifier":"scope-app-recursive","description":"This scope permits recursive access to the complete application folders, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"},{"path":"$APPDATA"},{"path":"$APPDATA/**"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"},{"path":"$APPCACHE"},{"path":"$APPCACHE/**"},{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-appcache":{"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"}]}},"scope-appcache-recursive":{"identifier":"scope-appcache-recursive","description":"This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"}]}},"scope-appconfig-recursive":{"identifier":"scope-appconfig-recursive","description":"This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"}]}},"scope-appdata-recursive":{"identifier":"scope-appdata-recursive","description":"This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"}]}},"scope-applocaldata-recursive":{"identifier":"scope-applocaldata-recursive","description":"This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-applog-index":{"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"}]}},"scope-applog-recursive":{"identifier":"scope-applog-recursive","description":"This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-audio":{"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/*"}]}},"scope-audio-index":{"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"}]}},"scope-audio-recursive":{"identifier":"scope-audio-recursive","description":"This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/**"}]}},"scope-cache":{"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/*"}]}},"scope-cache-index":{"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"}]}},"scope-cache-recursive":{"identifier":"scope-cache-recursive","description":"This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/**"}]}},"scope-config":{"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/*"}]}},"scope-config-index":{"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"}]}},"scope-config-recursive":{"identifier":"scope-config-recursive","description":"This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/**"}]}},"scope-data":{"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/*"}]}},"scope-data-index":{"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"}]}},"scope-data-recursive":{"identifier":"scope-data-recursive","description":"This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/**"}]}},"scope-desktop":{"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"}]}},"scope-desktop-recursive":{"identifier":"scope-desktop-recursive","description":"This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/**"}]}},"scope-document":{"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"}]}},"scope-document-recursive":{"identifier":"scope-document-recursive","description":"This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/**"}]}},"scope-download":{"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"}]}},"scope-download-recursive":{"identifier":"scope-download-recursive","description":"This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/*"}]}},"scope-exe-index":{"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"}]}},"scope-exe-recursive":{"identifier":"scope-exe-recursive","description":"This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/**"}]}},"scope-font":{"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/*"}]}},"scope-font-index":{"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"}]}},"scope-font-recursive":{"identifier":"scope-font-recursive","description":"This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/**"}]}},"scope-home":{"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/*"}]}},"scope-home-index":{"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"}]}},"scope-home-recursive":{"identifier":"scope-home-recursive","description":"This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/**"}]}},"scope-localdata":{"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"}]}},"scope-localdata-recursive":{"identifier":"scope-localdata-recursive","description":"This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/**"}]}},"scope-log":{"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/*"}]}},"scope-log-index":{"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"}]}},"scope-log-recursive":{"identifier":"scope-log-recursive","description":"This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/**"}]}},"scope-picture":{"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/*"}]}},"scope-picture-index":{"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"}]}},"scope-picture-recursive":{"identifier":"scope-picture-recursive","description":"This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/**"}]}},"scope-public":{"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/*"}]}},"scope-public-index":{"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"}]}},"scope-public-recursive":{"identifier":"scope-public-recursive","description":"This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/**"}]}},"scope-resource":{"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"}]}},"scope-resource-recursive":{"identifier":"scope-resource-recursive","description":"This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/**"}]}},"scope-runtime":{"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"}]}},"scope-runtime-recursive":{"identifier":"scope-runtime-recursive","description":"This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/**"}]}},"scope-temp":{"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/*"}]}},"scope-temp-index":{"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"}]}},"scope-temp-recursive":{"identifier":"scope-temp-recursive","description":"This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/**"}]}},"scope-template":{"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"}]}},"scope-template-recursive":{"identifier":"scope-template-recursive","description":"This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/**"}]}},"scope-video":{"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/*"}]}},"scope-video-index":{"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"}]}},"scope-video-recursive":{"identifier":"scope-video-recursive","description":"This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/**"}]}},"write-all":{"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}},"write-files":{"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows non-recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows full recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the application folders.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete application folders, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the application folders.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recursive write access to the complete application folders, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\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"},{"properties":{"path":{"description":"A path that can be accessed by the webview when using the fs 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"}},"required":["path"],"type":"object"}],"description":"FS scope entry.","title":"FsScopeEntry"}},"global-shortcut":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n","permissions":[]},"permissions":{"allow-is-registered":{"identifier":"allow-is-registered","description":"Enables the is_registered command without any pre-configured scope.","commands":{"allow":["is_registered"],"deny":[]}},"allow-register":{"identifier":"allow-register","description":"Enables the register command without any pre-configured scope.","commands":{"allow":["register"],"deny":[]}},"allow-register-all":{"identifier":"allow-register-all","description":"Enables the register_all command without any pre-configured scope.","commands":{"allow":["register_all"],"deny":[]}},"allow-unregister":{"identifier":"allow-unregister","description":"Enables the unregister command without any pre-configured scope.","commands":{"allow":["unregister"],"deny":[]}},"allow-unregister-all":{"identifier":"allow-unregister-all","description":"Enables the unregister_all command without any pre-configured scope.","commands":{"allow":["unregister_all"],"deny":[]}},"deny-is-registered":{"identifier":"deny-is-registered","description":"Denies the is_registered command without any pre-configured scope.","commands":{"allow":[],"deny":["is_registered"]}},"deny-register":{"identifier":"deny-register","description":"Denies the register command without any pre-configured scope.","commands":{"allow":[],"deny":["register"]}},"deny-register-all":{"identifier":"deny-register-all","description":"Denies the register_all command without any pre-configured scope.","commands":{"allow":[],"deny":["register_all"]}},"deny-unregister":{"identifier":"deny-unregister","description":"Denies the unregister command without any pre-configured scope.","commands":{"allow":[],"deny":["unregister"]}},"deny-unregister-all":{"identifier":"deny-unregister-all","description":"Denies the unregister_all command without any pre-configured scope.","commands":{"allow":[],"deny":["unregister_all"]}}},"permission_sets":{},"global_scope_schema":null},"os":{"default_permission":{"identifier":"default","description":"This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n","permissions":["allow-arch","allow-exe-extension","allow-family","allow-locale","allow-os-type","allow-platform","allow-version"]},"permissions":{"allow-arch":{"identifier":"allow-arch","description":"Enables the arch command without any pre-configured scope.","commands":{"allow":["arch"],"deny":[]}},"allow-exe-extension":{"identifier":"allow-exe-extension","description":"Enables the exe_extension command without any pre-configured scope.","commands":{"allow":["exe_extension"],"deny":[]}},"allow-family":{"identifier":"allow-family","description":"Enables the family command without any pre-configured scope.","commands":{"allow":["family"],"deny":[]}},"allow-hostname":{"identifier":"allow-hostname","description":"Enables the hostname command without any pre-configured scope.","commands":{"allow":["hostname"],"deny":[]}},"allow-locale":{"identifier":"allow-locale","description":"Enables the locale command without any pre-configured scope.","commands":{"allow":["locale"],"deny":[]}},"allow-os-type":{"identifier":"allow-os-type","description":"Enables the os_type command without any pre-configured scope.","commands":{"allow":["os_type"],"deny":[]}},"allow-platform":{"identifier":"allow-platform","description":"Enables the platform command without any pre-configured scope.","commands":{"allow":["platform"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-arch":{"identifier":"deny-arch","description":"Denies the arch command without any pre-configured scope.","commands":{"allow":[],"deny":["arch"]}},"deny-exe-extension":{"identifier":"deny-exe-extension","description":"Denies the exe_extension command without any pre-configured scope.","commands":{"allow":[],"deny":["exe_extension"]}},"deny-family":{"identifier":"deny-family","description":"Denies the family command without any pre-configured scope.","commands":{"allow":[],"deny":["family"]}},"deny-hostname":{"identifier":"deny-hostname","description":"Denies the hostname command without any pre-configured scope.","commands":{"allow":[],"deny":["hostname"]}},"deny-locale":{"identifier":"deny-locale","description":"Denies the locale command without any pre-configured scope.","commands":{"allow":[],"deny":["locale"]}},"deny-os-type":{"identifier":"deny-os-type","description":"Denies the os_type command without any pre-configured scope.","commands":{"allow":[],"deny":["os_type"]}},"deny-platform":{"identifier":"deny-platform","description":"Denies the platform command without any pre-configured scope.","commands":{"allow":[],"deny":["platform"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}},"updater":{"default_permission":{"identifier":"default","description":"This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n","permissions":["allow-check","allow-download","allow-install","allow-download-and-install"]},"permissions":{"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-download":{"identifier":"allow-download","description":"Enables the download command without any pre-configured scope.","commands":{"allow":["download"],"deny":[]}},"allow-download-and-install":{"identifier":"allow-download-and-install","description":"Enables the download_and_install command without any pre-configured scope.","commands":{"allow":["download_and_install"],"deny":[]}},"allow-install":{"identifier":"allow-install","description":"Enables the install command without any pre-configured scope.","commands":{"allow":["install"],"deny":[]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-download":{"identifier":"deny-download","description":"Denies the download command without any pre-configured scope.","commands":{"allow":[],"deny":["download"]}},"deny-download-and-install":{"identifier":"deny-download-and-install","description":"Denies the download_and_install command without any pre-configured scope.","commands":{"allow":[],"deny":["download_and_install"]}},"deny-install":{"identifier":"deny-install","description":"Denies the install command without any pre-configured scope.","commands":{"allow":[],"deny":["install"]}}},"permission_sets":{},"global_scope_schema":null}}
\ No newline at end of file
diff --git a/apps/tauri/src-tauri/gen/schemas/capabilities.json b/apps/tauri/src-tauri/gen/schemas/capabilities.json
index 3a040a73e3b5..a35e3480ee49 100644
--- a/apps/tauri/src-tauri/gen/schemas/capabilities.json
+++ b/apps/tauri/src-tauri/gen/schemas/capabilities.json
@@ -1 +1 @@
-{"default":{"identifier":"default","description":"Default permissions for Spacedrive","local":true,"windows":["main","inspector-*","quick-preview-*","settings-*","job-manager"],"permissions":["core:default","core:event:allow-listen","core:event:allow-emit","core:window:allow-create","core:window:allow-close","core:window:allow-get-all-windows","core:window:allow-start-dragging","core:webview:allow-create-webview-window","core:path:default","dialog:allow-open","dialog:allow-save","shell:allow-open","fs:allow-home-read-recursive","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","updater:default"]}}
\ No newline at end of file
+{"default":{"identifier":"default","description":"Default permissions for Spacedrive","local":true,"windows":["main","spacebot","voice-overlay","inspector-*","quick-preview-*","settings-*","job-manager"],"permissions":["core:default","core:event:allow-listen","core:event:allow-emit","core:window:allow-create","core:window:allow-close","core:window:allow-get-all-windows","core:window:allow-start-dragging","core:webview:allow-create-webview-window","core:path:default","dialog:allow-open","dialog:allow-save","shell:allow-open","fs:allow-home-read-recursive","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","updater:default"]}}
\ No newline at end of file
diff --git a/apps/tauri/src-tauri/gen/schemas/desktop-schema.json b/apps/tauri/src-tauri/gen/schemas/desktop-schema.json
index 14004e9f7a7c..14131eba3c04 100644
--- a/apps/tauri/src-tauri/gen/schemas/desktop-schema.json
+++ b/apps/tauri/src-tauri/gen/schemas/desktop-schema.json
@@ -6026,6 +6026,72 @@
"const": "fs:write-files",
"markdownDescription": "This enables all file write related commands without any pre-configured accessible paths."
},
+ {
+ "description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
+ "type": "string",
+ "const": "global-shortcut:default",
+ "markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
+ },
+ {
+ "description": "Enables the is_registered command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-is-registered",
+ "markdownDescription": "Enables the is_registered command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the register command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-register",
+ "markdownDescription": "Enables the register command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the register_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-register-all",
+ "markdownDescription": "Enables the register_all command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the unregister command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-unregister",
+ "markdownDescription": "Enables the unregister command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the unregister_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-unregister-all",
+ "markdownDescription": "Enables the unregister_all command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the is_registered command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-is-registered",
+ "markdownDescription": "Denies the is_registered command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the register command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-register",
+ "markdownDescription": "Denies the register command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the register_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-register-all",
+ "markdownDescription": "Denies the register_all command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the unregister command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-unregister",
+ "markdownDescription": "Denies the unregister command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the unregister_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-unregister-all",
+ "markdownDescription": "Denies the unregister_all command without any pre-configured scope."
+ },
{
"description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n\n#### This default permission set includes:\n\n- `allow-arch`\n- `allow-exe-extension`\n- `allow-family`\n- `allow-locale`\n- `allow-os-type`\n- `allow-platform`\n- `allow-version`",
"type": "string",
diff --git a/apps/tauri/src-tauri/gen/schemas/macOS-schema.json b/apps/tauri/src-tauri/gen/schemas/macOS-schema.json
index 14004e9f7a7c..14131eba3c04 100644
--- a/apps/tauri/src-tauri/gen/schemas/macOS-schema.json
+++ b/apps/tauri/src-tauri/gen/schemas/macOS-schema.json
@@ -6026,6 +6026,72 @@
"const": "fs:write-files",
"markdownDescription": "This enables all file write related commands without any pre-configured accessible paths."
},
+ {
+ "description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
+ "type": "string",
+ "const": "global-shortcut:default",
+ "markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
+ },
+ {
+ "description": "Enables the is_registered command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-is-registered",
+ "markdownDescription": "Enables the is_registered command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the register command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-register",
+ "markdownDescription": "Enables the register command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the register_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-register-all",
+ "markdownDescription": "Enables the register_all command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the unregister command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-unregister",
+ "markdownDescription": "Enables the unregister command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the unregister_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:allow-unregister-all",
+ "markdownDescription": "Enables the unregister_all command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the is_registered command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-is-registered",
+ "markdownDescription": "Denies the is_registered command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the register command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-register",
+ "markdownDescription": "Denies the register command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the register_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-register-all",
+ "markdownDescription": "Denies the register_all command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the unregister command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-unregister",
+ "markdownDescription": "Denies the unregister command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the unregister_all command without any pre-configured scope.",
+ "type": "string",
+ "const": "global-shortcut:deny-unregister-all",
+ "markdownDescription": "Denies the unregister_all command without any pre-configured scope."
+ },
{
"description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n\n#### This default permission set includes:\n\n- `allow-arch`\n- `allow-exe-extension`\n- `allow-family`\n- `allow-locale`\n- `allow-os-type`\n- `allow-platform`\n- `allow-version`",
"type": "string",
diff --git a/apps/tauri/src-tauri/icons/128x128.png b/apps/tauri/src-tauri/icons/128x128.png
index c7266c7280a8..1dda499fc226 100644
Binary files a/apps/tauri/src-tauri/icons/128x128.png and b/apps/tauri/src-tauri/icons/128x128.png differ
diff --git a/apps/tauri/src-tauri/icons/128x128@2x.png b/apps/tauri/src-tauri/icons/128x128@2x.png
index 3822a5c617d6..f753c7bf5e11 100644
Binary files a/apps/tauri/src-tauri/icons/128x128@2x.png and b/apps/tauri/src-tauri/icons/128x128@2x.png differ
diff --git a/apps/tauri/src-tauri/icons/32x32.png b/apps/tauri/src-tauri/icons/32x32.png
index 7a31a5c45977..97da93ce6640 100644
Binary files a/apps/tauri/src-tauri/icons/32x32.png and b/apps/tauri/src-tauri/icons/32x32.png differ
diff --git a/apps/tauri/src-tauri/icons/icon.icns b/apps/tauri/src-tauri/icons/icon.icns
index c0ac005ce596..e066ccc55191 100644
Binary files a/apps/tauri/src-tauri/icons/icon.icns and b/apps/tauri/src-tauri/icons/icon.icns differ
diff --git a/apps/tauri/src-tauri/icons/icon.ico b/apps/tauri/src-tauri/icons/icon.ico
index b7878b6d342d..841913a53376 100644
Binary files a/apps/tauri/src-tauri/icons/icon.ico and b/apps/tauri/src-tauri/icons/icon.ico differ
diff --git a/apps/tauri/src-tauri/src/main.rs b/apps/tauri/src-tauri/src/main.rs
index 86361e315582..8153efe10fa4 100644
--- a/apps/tauri/src-tauri/src/main.rs
+++ b/apps/tauri/src-tauri/src/main.rs
@@ -16,6 +16,7 @@ use std::sync::Arc;
use tauri::menu::MenuItem;
use tauri::Emitter;
use tauri::{AppHandle, Manager};
+use tauri_plugin_global_shortcut::ShortcutState;
use tokio::sync::oneshot;
use tokio::sync::RwLock;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@@ -350,6 +351,18 @@ struct MenuState {
async fn app_ready(window: tauri::Window) {
window.show().ok();
window.set_focus().ok();
+
+ // #[cfg(debug_assertions)]
+ // if window.label() == "main" && window.app_handle().get_webview_window("spacebot").is_none() {
+ // if let Err(error) = windows::show_window(
+ // window.app_handle().clone(),
+ // windows::SpacedriveWindow::Spacebot,
+ // )
+ // .await
+ // {
+ // tracing::warn!(?error, "Failed to auto-open Spacebot window");
+ // }
+ // }
}
/// Get the daemon socket address for the frontend to connect
@@ -533,9 +546,15 @@ async fn validate_and_reset_library_if_needed(
// Parse response to get library list
let libraries: Vec = response
- .get("JsonOk").or_else(|| response.get("result"))
+ .get("JsonOk")
+ .or_else(|| response.get("result"))
.and_then(|r| r.as_array())
- .ok_or_else(|| format!("Invalid response format from libraries.list query. Raw: {}", response_line.trim()))?
+ .ok_or_else(|| {
+ format!(
+ "Invalid response format from libraries.list query. Raw: {}",
+ response_line.trim()
+ )
+ })?
.clone();
// Check if current library ID exists in the list
@@ -1662,11 +1681,7 @@ fn setup_menu(app: &AppHandle) -> Result<(), Box> {
.build(app)?;
menu_items_map.insert("copy".to_string(), copy_item.clone());
- let paste_item = MenuItemBuilder::with_id("paste", "Paste")
- .accelerator("Cmd+V")
- .enabled(true)
- .build(app)?;
- menu_items_map.insert("paste".to_string(), paste_item.clone());
+ let paste_item = PredefinedMenuItem::paste(app, None)?;
let edit_menu = SubmenuBuilder::new(app, "Edit")
.item(&PredefinedMenuItem::undo(app, None)?)
@@ -1873,8 +1888,9 @@ fn setup_menu(app: &AppHandle) -> Result<(), Box> {
tracing::error!("Failed to emit menu action: {}", e);
}
}
- // Edit menu clipboard actions - emit event for smart handling in frontend
- "cut" | "copy" | "paste" => {
+ // Edit menu clipboard actions - emit event for smart handling in frontend.
+ // Paste uses the native predefined menu item to avoid duplicate text pastes.
+ "cut" | "copy" => {
tracing::info!("[Menu] Clipboard action triggered: {}", event_id);
// Emit generic clipboard event - frontend will decide if it's a text or file operation
if let Err(e) = app_handle.emit("clipboard-action", event_id) {
@@ -1910,6 +1926,22 @@ fn main() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
+ .plugin(
+ tauri_plugin_global_shortcut::Builder::new()
+ .with_shortcut("Alt+Space")
+ .expect("failed to register Alt+Space global shortcut")
+ .with_handler(|app, _shortcut, event| {
+ if event.state() == ShortcutState::Pressed {
+ if let Err(error) = windows::toggle_voice_overlay_internal(app.clone()) {
+ tracing::warn!(
+ ?error,
+ "Failed to toggle voice overlay from global shortcut"
+ );
+ }
+ }
+ })
+ .build(),
+ )
.invoke_handler(tauri::generate_handler![
app_ready,
get_daemon_socket,
@@ -1934,8 +1966,10 @@ fn main() {
open_macos_settings,
windows::show_window,
windows::close_window,
+ windows::toggle_voice_overlay,
windows::list_windows,
windows::apply_macos_styling,
+ windows::resize_overlay_window,
windows::position_context_menu,
drag::begin_drag,
drag::end_drag,
diff --git a/apps/tauri/src-tauri/src/windows.rs b/apps/tauri/src-tauri/src/windows.rs
index 342c4023c7c5..a7fb28d56fba 100644
--- a/apps/tauri/src-tauri/src/windows.rs
+++ b/apps/tauri/src-tauri/src/windows.rs
@@ -23,6 +23,7 @@ pub enum SpacedriveWindow {
},
JobManager,
DeviceDiscovery,
+ Spacebot,
/// Floating panels (always on top)
Inspector {
@@ -38,6 +39,7 @@ pub enum SpacedriveWindow {
/// Floating controls (small, always on top)
FloatingControls,
+ VoiceOverlay,
/// Drag demo window
DragDemo,
@@ -68,6 +70,7 @@ impl SpacedriveWindow {
Self::Settings { page } => format!("settings-{}", page.as_deref().unwrap_or("general")),
Self::JobManager => "job-manager".to_string(),
Self::DeviceDiscovery => "device-discovery".to_string(),
+ Self::Spacebot => "spacebot".to_string(),
Self::Inspector { item_id } => {
format!("inspector-{}", item_id.as_deref().unwrap_or("floating"))
}
@@ -75,6 +78,7 @@ impl SpacedriveWindow {
Self::TagAssignment => "tag-assignment".to_string(),
Self::SearchOverlay => "search-overlay".to_string(),
Self::FloatingControls => "floating-controls".to_string(),
+ Self::VoiceOverlay => "voice-overlay".to_string(),
Self::DragDemo => "drag-demo".to_string(),
Self::Spacedrop => "spacedrop".to_string(),
Self::DragOverlay { session_id } => format!("drag-overlay-{}", session_id),
@@ -214,6 +218,18 @@ impl SpacedriveWindow {
false,
),
+ Self::Spacebot => create_window(
+ app,
+ &label,
+ "/spacebot",
+ "Spacebot",
+ (1200.0, 800.0),
+ (800.0, 600.0),
+ true,
+ false,
+ false,
+ ),
+
Self::QuickPreview { file_id } => {
let url = format!("/quick-preview/{}", file_id);
create_window(
@@ -295,6 +311,27 @@ impl SpacedriveWindow {
Ok(window)
}
+ Self::VoiceOverlay => {
+ let window =
+ WebviewWindowBuilder::new(app, label, WebviewUrl::App("/voice-overlay".into()))
+ .title("Voice Overlay")
+ .inner_size(520.0, 112.0)
+ .resizable(false)
+ .decorations(false)
+ .shadow(false)
+ .transparent(true)
+ .always_on_top(true)
+ .skip_taskbar(true)
+ .visible(false)
+ .build()
+ .map_err(|e| format!("Failed to create voice overlay: {}", e))?;
+
+ position_overlay_window(&window, 520.0, 112.0)?;
+
+ window.show().ok();
+ Ok(window)
+ }
+
Self::DragDemo => create_window(
app,
&label,
@@ -421,6 +458,30 @@ fn hash_string(s: &str) -> String {
format!("{:x}", hasher.finish())
}
+fn position_overlay_window(window: &WebviewWindow, width: f64, height: f64) -> Result<(), String> {
+ use tauri::{PhysicalPosition, Position};
+
+ let monitor = window
+ .current_monitor()
+ .map_err(|e| e.to_string())?
+ .ok_or("No monitor found")?;
+
+ let monitor_size = monitor.size();
+ let monitor_position = monitor.position();
+ let scale_factor = window.scale_factor().map_err(|e| e.to_string())?;
+
+ let physical_width = (width * scale_factor).round() as i32;
+ let physical_height = (height * scale_factor).round() as i32;
+ let bottom_margin = (24.0 * scale_factor).round() as i32;
+
+ let x = monitor_position.x + (monitor_size.width as i32 - physical_width) / 2;
+ let y = monitor_position.y + monitor_size.height as i32 - physical_height - bottom_margin;
+
+ window
+ .set_position(Position::Physical(PhysicalPosition::new(x, y)))
+ .map_err(|e| e.to_string())
+}
+
/// Tauri command to show a window
#[tauri::command]
pub async fn show_window(app: AppHandle, window: SpacedriveWindow) -> Result {
@@ -438,6 +499,51 @@ pub async fn close_window(app: AppHandle, label: String) -> Result<(), String> {
Ok(())
}
+pub fn toggle_voice_overlay_internal(app: AppHandle) -> Result<(), String> {
+ let window = SpacedriveWindow::VoiceOverlay;
+ let label = window.label();
+
+ if let Some(existing) = app.get_webview_window(&label) {
+ existing.close().map_err(|e| e.to_string())?;
+ return Ok(());
+ }
+
+ tauri::async_runtime::spawn(async move {
+ if let Err(error) = window.show(&app).await {
+ tracing::warn!(?error, "Failed to open voice overlay window");
+ }
+ });
+
+ Ok(())
+}
+
+#[tauri::command]
+pub async fn toggle_voice_overlay(app: AppHandle) -> Result<(), String> {
+ toggle_voice_overlay_internal(app)
+}
+
+#[tauri::command]
+pub async fn resize_overlay_window(
+ app: AppHandle,
+ label: String,
+ width: f64,
+ height: f64,
+) -> Result<(), String> {
+ use tauri::{LogicalSize, Size};
+
+ let window = app
+ .get_webview_window(&label)
+ .ok_or("Overlay window not found")?;
+
+ window
+ .set_size(Size::Logical(LogicalSize::new(width, height)))
+ .map_err(|e| e.to_string())?;
+
+ position_overlay_window(&window, width, height)?;
+
+ Ok(())
+}
+
/// Apply macOS window styling to current window (called from frontend when ready)
#[tauri::command]
pub fn apply_macos_styling(app: AppHandle) -> Result<(), String> {
diff --git a/apps/tauri/src/App.tsx b/apps/tauri/src/App.tsx
index ef7ddc3e7bf5..d826cb0afc6a 100644
--- a/apps/tauri/src/App.tsx
+++ b/apps/tauri/src/App.tsx
@@ -7,19 +7,28 @@ import {
PopoutInspector,
QuickPreview,
JobsScreen,
+ SpacebotProvider,
+ SpacebotLayout,
+ ChatRoute,
+ ConversationRoute,
+ TasksRoute,
+ MemoriesRoute,
+ AutonomyRoute,
+ ScheduleRoute,
+ VoiceOverlay,
Settings,
PlatformProvider,
SpacedriveProvider,
ServerProvider,
JobsProvider,
} from "@sd/interface";
+import {createMemoryRouter, Navigate, Outlet, RouterProvider} from "react-router-dom";
import {
SpacedriveClient,
TauriTransport,
useSyncPreferencesStore,
} from "@sd/ts-client";
import type { Event as CoreEvent } from "@sd/ts-client";
-import { sounds } from "@sd/assets/sounds";
import { useEffect, useState } from "react";
import { DragOverlay } from "./routes/DragOverlay";
import { ContextMenuWindow } from "./routes/ContextMenuWindow";
@@ -29,10 +38,28 @@ import { platform } from "./platform";
import { initializeContextMenuHandler } from "./contextMenu";
import { initializeKeybindGlobal } from "./keybinds";
+function getInitialRoute() {
+ const label = getCurrentWebviewWindow().label;
+
+ if (label === "floating-controls") return "/floating-controls";
+ if (label.startsWith("drag-overlay")) return "/drag-overlay";
+ if (label.startsWith("context-menu")) return "/contextmenu";
+ if (label.startsWith("drag-demo")) return "/drag-demo";
+ if (label.startsWith("spacedrop")) return "/spacedrop";
+ if (label.startsWith("settings")) return "/settings";
+ if (label.startsWith("inspector")) return "/inspector";
+ if (label.startsWith("quick-preview")) return "/quick-preview";
+ if (label.startsWith("job-manager")) return "/job-manager";
+ if (label.startsWith("spacebot")) return "/spacebot";
+ if (label.startsWith("voice-overlay")) return "/voice-overlay";
+
+ return "/";
+}
+
function App() {
const [client, setClient] = useState(null);
const [error, setError] = useState(null);
- const [route, setRoute] = useState("/");
+ const [route, setRoute] = useState(getInitialRoute);
useEffect(() => {
// React Scan disabled - too heavy for development
@@ -68,25 +95,7 @@ function App() {
}
// Set route based on window label
- if (label === "floating-controls") {
- setRoute("/floating-controls");
- } else if (label.startsWith("drag-overlay")) {
- setRoute("/drag-overlay");
- } else if (label.startsWith("context-menu")) {
- setRoute("/contextmenu");
- } else if (label.startsWith("drag-demo")) {
- setRoute("/drag-demo");
- } else if (label.startsWith("spacedrop")) {
- setRoute("/spacedrop");
- } else if (label.startsWith("settings")) {
- setRoute("/settings");
- } else if (label.startsWith("inspector")) {
- setRoute("/inspector");
- } else if (label.startsWith("quick-preview")) {
- setRoute("/quick-preview");
- } else if (label.startsWith("job-manager")) {
- setRoute("/job-manager");
- }
+ setRoute(getInitialRoute());
// Tell Tauri window is ready to be shown
invoke("app_ready").catch(console.error);
@@ -294,6 +303,67 @@ function App() {
);
}
+ if (route === "/spacebot") {
+ const spacebotRouter = createMemoryRouter(
+ [
+ {
+ path: "/spacebot",
+ element: (
+
+
+
+ ),
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ element: ,
+ children: [
+ {
+ path: "chat",
+ children: [
+ {index: true, element: },
+ {path: "new", element: },
+ {path: "conversation/*", element: },
+ ],
+ },
+ {path: "tasks", element: },
+ {path: "memories", element: },
+ {path: "autonomy", element: },
+ {path: "schedule", element: },
+ ],
+ },
+ ],
+ },
+ ],
+ {
+ initialEntries: ["/spacebot"],
+ }
+ );
+
+ return (
+
+
+
+